--- phase: 14.1-cloud-local-file-parity-hardening plan: "04" subsystem: cloud-browse-parity-frontend status: complete tags: [backend, frontend, browse, parity, analysis-status, topics, cloud-detail-route, row-click] requirements: [CLOUD-02, ANALYZE-01, ANALYZE-02, ANALYZE-03, ANALYZE-04, ANALYZE-05, ANALYZE-06, CACHE-03] dependency_graph: requires: - 14.1-01 (RED tests — StorageBrowser.parity.test.js must go green) - 14.1-02 (backend detail endpoint and CloudItemOut schema) - 14.1-03 (cloud-file-detail route in router/index.js + CloudDetailView) provides: - backend/api/cloud/schemas.py (CloudItemOut gains topics, analysis_status, is_stale) - backend/api/cloud/browse.py (_item_out populates parity fields; _batch_load_topics) - frontend/src/components/storage/StorageBrowser.vue (row parity slots; store translator) - frontend/src/views/CloudFolderView.vue (onFileOpen navigates to cloud-file-detail) - frontend/src/views/__tests__/CloudFolderOpenPreview.test.js (updated to new behavior) affects: - frontend/src/views/CloudDetailView.vue (is the target of cloud row navigation) tech_stack: added: [] patterns: - Batch topic-name JOIN query to avoid N+1 browse rows (T-14.1-10) - Single analysis action slot in name cell (Analyze/Re-analyze/Retry by state — D-08/D-10) - Store translateAnalysisStatus as single status translation source (D-06) - Row click navigates to internal named route (cloud-file-detail); no auto preview/download (D-14) - Inline status badge in cloud file rows (green/amber/blue/red per UI-SPEC) key_files: created: [] modified: - backend/api/cloud/schemas.py - backend/api/cloud/browse.py - frontend/src/components/storage/StorageBrowser.vue - frontend/src/views/CloudFolderView.vue - frontend/src/views/__tests__/CloudFolderOpenPreview.test.js decisions: - CloudItemOut row fields (topics/analysis_status/is_stale) are an allowlist — no extracted_text (T-14.1-10) - _batch_load_topics uses a single JOIN over CloudItemTopic + Topic for all file ids in the page - Folder rows always receive analysis_status=None and topics=[] from _item_out - cloudRowActionKind() drives the single analysis action slot — exactly one of Analyze/Re-analyze/Retry renders at a time - translateStatus in StorageBrowser now delegates to cloudStore.translateAnalysisStatus (D-06 CLAUDE.md rule) - onFileOpen in CloudFolderView replaced with router.push to cloud-file-detail (D-01/D-14/CACHE-03) - CloudFolderOpenPreview.test.js updated to assert route navigation instead of openCloudFile calls metrics: duration: "15m" completed_date: "2026-06-26" tasks_completed: 2 files_created: 0 files_modified: 5 backend_tests_passing: 88 frontend_tests_passing: 489 test_suite_delta: +1 (pre-existing RED test for Re-analyze now green; 0 new failures) --- # Phase 14.1 Plan 04: Browse Row Parity + Cloud Row Navigation Summary Make cloud browser rows/cards match local rows: surface lightweight topics + analysis status + current/stale state from the browse endpoint, render them in the same relative slots in StorageBrowser, route cloud row clicks to the cloud detail route (not auto preview/download), and collapse the duplicate status translator so the store is the single source. ## Tasks Completed ### Task 1: Browse rows carry lightweight topics + analysis_status + stale; StorageBrowser renders parity slots via store translator **`backend/api/cloud/schemas.py`:** Extended `CloudItemOut` with three lightweight, allowlisted row fields: - `topics: List[str] = []` — topic name strings; empty for folders/unanalyzed files - `analysis_status: Optional[str] = None` — None for folder rows - `is_stale: bool = False` — True when `analysis_status == "stale"` (D-07 convenience flag) `extracted_text` is deliberately absent from `CloudItemOut` (T-14.1-10 allowlist rule — row payload must stay lightweight; full text stays on `CloudItemDetailOut` only). **`backend/api/cloud/browse.py`:** - Added `from db.models import CloudItemTopic, Topic` and `from sqlalchemy import select` imports. - `_item_out` updated to accept optional `topic_names: list[str] | None` and populate `analysis_status`, `is_stale`, and `topics` from the passed topics list. Folder rows always receive `analysis_status=None`, `topics=[]`. - Added `_batch_load_topics(session, file_ids)` — a single `SELECT cloud_item_topics.cloud_item_id, topics.name FROM cloud_item_topics JOIN topics ON ...` query over the page's file item ids. Returns a dict mapping `cloud_item_id → [str]`. Avoids N+1 queries (T-14.1-10). - `browse_connection_items` computes `file_ids`, calls `_batch_load_topics`, then passes `topic_names=topics_by_item.get(item.id)` to `_item_out` for each row. **`frontend/src/components/storage/StorageBrowser.vue`:** - Imported `useCloudConnectionsStore` and instantiated `cloudStore` at the top of `