19 KiB
phase, plan, type, wave, depends_on, files_modified, autonomous, requirements, user_setup, must_haves
| phase | plan | type | wave | depends_on | files_modified | autonomous | requirements | user_setup | must_haves | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 05-cloud-storage-backends | 08 | execute | 7 |
|
|
false |
|
|
|
Purpose: Complete the sidebar integration so users can navigate cloud storage alongside local folders. Human checkpoint verifies the UI renders correctly. Output: AppSidebar extended with cloud section; CloudProviderTreeItem; CloudFolderTreeItem.
<execution_context> @/Users/nik/.claude/get-shit-done/workflows/execute-plan.md @/Users/nik/.claude/get-shit-done/templates/summary.md </execution_context>
@.planning/PROJECT.md @.planning/ROADMAP.md @.planning/phases/05-cloud-storage-backends/05-CONTEXT.md @.planning/phases/05-cloud-storage-backends/05-UI-SPEC.md @.planning/phases/05-cloud-storage-backends/05-07-SUMMARY.md From AppSidebar.vue: existing Folders collapsible section (foldersExpanded, FolderTreeItem pattern) Pattern:FolderTreeItem: depth prop, toggle expand on arrow click, navigate on name click Pattern: paddingLeft = depth * 12 + 'px', rotate-90 class on expand, lazy-load children
Exact markup for cloud section header, cloudExpanded toggle, CloudProviderTreeItem rendering providerIconColor: google_drive=text-blue-500, onedrive=text-sky-500, nextcloud=text-orange-500, webdav=text-gray-500 depth * 12 px left padding formula
useCloudConnectionsStore: connections (ref[]), fetchConnections() activeCloudConnections = connections.filter(c => c.status === "ACTIVE")
getCloudFolders(provider, folderId): GET /api/cloud/folders/{provider}/{folderId} Returns: { items: [{id, name, is_dir, size}, ...] }
Task 1: Create CloudProviderTreeItem, CloudFolderTreeItem, and add API function frontend/src/components/cloud/CloudProviderTreeItem.vue, frontend/src/components/cloud/CloudFolderTreeItem.vue, frontend/src/api/client.js - frontend/src/components/folders/FolderTreeItem.vue — lazy-load tree item pattern (expand toggle, depth padding, children loading) - frontend/src/api/client.js — request() pattern for new getCloudFolders function - .planning/phases/05-cloud-storage-backends/05-UI-SPEC.md — Surface 5 exact component markup CloudProviderTreeItem.vue: - Props: connection (Object: {id, provider, display_name, status}), depth (Number, default 1) - Local state: expanded (ref false), children (ref []), loading (ref false), loadError (ref false) - On toggle expand: if !expanded and children.length==0, fetch via api.getCloudFolders(connection.provider, 'root'); set loading during fetch; set loadError on error - On retry click (load error state): re-fetch children - providerIconColor computed from connection.provider (map per UI-SPEC) - navigate to cloud folder root on name click — emit 'navigate' or use router.push('/cloud/{provider}/root') - Renders CloudFolderTreeItem for each childCloudFolderTreeItem.vue:
- Props: folder (Object: {id, name, is_dir, size}), provider (String), depth (Number)
- Local state: expanded (ref false), children (ref []), loading (ref false), loadError (ref false)
- Only renders expand arrow if folder.is_dir === true
- On toggle expand: fetch api.getCloudFolders(provider, folder.id); same loading/error pattern
- Indentation: depth * 12 px
- Navigate to /cloud/{provider}/{folder.id} on click (router.push)
Add to frontend/src/api/client.js (append after disconnectCloud/connectWebDav):
export function getCloudFolders(provider, folderId) {
return request(`/api/cloud/folders/${provider}/${folderId}`)
}
Create frontend/src/components/cloud/CloudProviderTreeItem.vue following the UI-SPEC Surface 5 exact markup:
- Template mirrors FolderTreeItem structure: expand arrow button + name button
- Expand arrow: svg chevron, rotate-90 when expanded
- Provider name button: uses providerIconColor, active/hover classes per UI-SPEC
- Loading state: text-xs text-gray-400 "Loading…" at pl-12
- Error state: text-xs text-red-500 "Failed to load — tap to retry" with @click=retry
- Children loop: CloudFolderTreeItem for each child in children
- paddingLeft style: `${depth * 12}px`
Create frontend/src/components/cloud/CloudFolderTreeItem.vue:
- Simpler than CloudProviderTreeItem: folder icon (text-gray-400), name, expand arrow if is_dir
- Same loading/error pattern as CloudProviderTreeItem
- Navigate via router.push on name click
- Recursively renders CloudFolderTreeItem for nested children
- paddingLeft style: `${depth * 12}px`
Both components use Options API (consistent with existing Phase 4 components) or Composition API with script setup — match the style used in FolderTreeItem.vue (whichever pattern it uses).
Import in script section:
import CloudProviderTreeItem from '../cloud/CloudProviderTreeItem.vue'
import { useCloudConnectionsStore } from '../../stores/cloudConnections.js'
Add to reactive data / setup:
cloudExpanded = ref(true) (or data() equivalent)
cloudConnectionsStore = useCloudConnectionsStore()
Add computed:
activeCloudConnections: return cloudConnectionsStore.connections.filter(c => c.status === 'ACTIVE')
loadingCloudConnections: return cloudConnectionsStore.loading
In onMounted (or mounted lifecycle):
cloudConnectionsStore.fetchConnections()
Insert cloud section template per UI-SPEC Surface 5 exact markup:
- Section header with cloud icon (SVG cloud path d="M3 15a4 4 0 004 4h9a5 5 0 10-.1-9.999 5.002 5.002 0 10-9.78 2.096A4.001 4.001 0 003 15z")
- class="w-4 h-4 mr-2 shrink-0 text-sky-500" on cloud SVG
- a href="/settings" with nav-link class for "Cloud Storage" label
- CloudProviderTreeItem v-for over activeCloudConnections
- Loading and empty state text per behavior spec
<threat_model>
Trust Boundaries
| Boundary | Description |
|---|---|
| Sidebar → /api/cloud/folders | Cloud folder listings loaded via authenticated API; no direct provider calls from browser |
| window.location.href → /api/cloud/oauth/initiate | OAuth redirect is a browser navigation — no token in JavaScript |
STRIDE Threat Register
| Threat ID | Category | Component | Disposition | Mitigation Plan |
|---|---|---|---|---|
| T-05-08-01 | Information Disclosure | CloudProviderTreeItem — folder names in DOM | accept | Folder names are user's own cloud content; displayed only to authenticated user; no PII or credentials |
| T-05-08-02 | Denial of Service | Sidebar fetch on mount | mitigate | fetchConnections called once on AppSidebar mount; TTLCache on server prevents repeated API calls for folder listings within 60s |
| T-05-08-03 | Spoofing | CloudFolderTreeItem folder navigation URL | accept | Route /cloud/{provider}/{folder_id} uses folder_id from API response; never from user-typed input |
| T-05-08-04 | Information Disclosure | AppSidebar shows ACTIVE connections | mitigate | Only ACTIVE connections shown; REQUIRES_REAUTH/ERROR hidden from sidebar (user directed to Settings to resolve) |
| </threat_model> |
<success_criteria>
- CloudProviderTreeItem.vue: provider icon colors, expand/collapse, lazy-load children, loading/error states
- CloudFolderTreeItem.vue: folder icon, is_dir expand, lazy-load nested, depth padding
- AppSidebar.vue: Cloud Storage section after Folders; cloudExpanded; CloudProviderTreeItem v-for over ACTIVE connections
- Vite build passes with 0 errors
- pytest -v (backend): 0 failures
- Human checkpoint: user confirms cloud section visible in sidebar; WebDAV SSRF rejection works; tests pass </success_criteria>