docs(14.1): create phase plan — cloud/local file parity hardening
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 4.6
parent
4873a22f9b
commit
f50cce61cb
+47
-2
@@ -1,7 +1,7 @@
|
||||
# DocuVault Roadmap: v0.3 Reimagining Cloud Storage integration
|
||||
|
||||
**Status:** Proposed
|
||||
**Phases:** 12-16
|
||||
**Phases:** 12-16 plus inserted 12.1, 14.1, and 14.2
|
||||
**Requirements:** 36
|
||||
**Defined:** 2026-06-17
|
||||
|
||||
@@ -26,6 +26,8 @@ Before any phase is marked complete:
|
||||
| 12 | 6/6 | Complete | 2026-06-21 |
|
||||
| 13 | 11/11 | Complete | 2026-06-23 |
|
||||
| 14 | 9/9 | Complete | 2026-06-23 |
|
||||
| 14.1 | Cloud/Local File Parity Hardening | Make cloud open, view, download, analyze, and analyzed-file display match local-file behavior end to end | CLOUD-02, ANALYZE-01..07, CACHE-03..05 |
|
||||
| 14.2 | Cross-Codebase Review and Cleanup | Cross-reference backend/frontend code, remove duplication and dead code, consolidate shared paths, and fix inefficiencies without behavior changes | Cross-cutting quality gates |
|
||||
| 15 | Unified Smart Search | Search local and analyzed cloud documents by exact text or semantic ideas without downloading files during queries | SEARCH-01..07 |
|
||||
| 16 | Change Tracking and Reliability | Detect external provider changes, mark stale indexes, remove deleted items, and harden refresh behavior | SYNC-02..04 |
|
||||
|
||||
@@ -134,11 +136,54 @@ Plans:
|
||||
4. Cloud bytes are cached only for active opening, preview, or analysis work and are evicted by configurable limits without interrupting pinned active jobs.
|
||||
5. Cache ownership, cache-key versioning, cancellation, duplicate-job, failure-retry, and eviction behavior have dedicated tests.
|
||||
|
||||
### Phase 14.1: Cloud/Local File Parity Hardening (INSERTED)
|
||||
|
||||
**Goal:** Cloud files opened, viewed, downloaded, analyzed, and displayed in results use the same user-facing behavior as local files, while preserving provider ownership, cache boundaries, authorization, and no-provider-mutation guarantees.
|
||||
**Requirements:** CLOUD-02, ANALYZE-01, ANALYZE-02, ANALYZE-03, ANALYZE-04, ANALYZE-05, ANALYZE-06, ANALYZE-07, CACHE-03, CACHE-04, CACHE-05
|
||||
**Depends on:** Phase 14
|
||||
**Plans:** 5 plans
|
||||
**Execution waves:** Wave 1: 14.1-01 (RED tests); Wave 2: 14.1-02 after 01 (backend detail/force/retry); Wave 3: 14.1-03 after 01-02 (shared detail surface + cloud route/view); Wave 4: 14.1-04 after 01-03 (browser row parity + cloud row navigation); Wave 5: 14.1-05 after 01-04 (gates, security, docs, version, commit).
|
||||
|
||||
Plans:
|
||||
|
||||
- [ ] 14.1-01-PLAN.md — RED backend + frontend parity tests (cloud detail endpoint, force re-analyze, single-item retry, route + row parity).
|
||||
- [ ] 14.1-02-PLAN.md — Backend cloud detail endpoint (CloudItemDetailOut + resolve_owned_cloud_item_detail), force re-analyze flag, single-item retry-job creation.
|
||||
- [ ] 14.1-03-PLAN.md — Shared DocumentDetailSurface extraction, DocumentView refactor (Re-analyze copy), CloudDetailView + cloud-file-detail route + getCloudItemDetail.
|
||||
- [ ] 14.1-04-PLAN.md — Browse row topics/analysis_status/stale, StorageBrowser row parity via store translator, cloud row click → cloud detail route (no auto preview/download).
|
||||
- [ ] 14.1-05-PLAN.md — Full test suites, security gate, dependency/secret audits, CLAUDE.md/README updates, version bump, atomic commit.
|
||||
|
||||
**Success Criteria:**
|
||||
|
||||
1. Cloud and local file rows/cards expose equivalent open, view, download, analyze, retry, and reanalyze states through `StorageBrowser.vue`.
|
||||
2. Analyzed cloud files surface extracted text, topics, analysis status, stale/current state, and retry/reanalyze affordances wherever equivalent local file data appears.
|
||||
3. Authorized cloud open, preview, and download never expose provider URLs or credentials and hydrate bytes only through the existing cache lifecycle.
|
||||
4. Unsupported or provider-limited cloud actions use typed responses and shared UI states instead of creating local/cloud UX forks.
|
||||
5. End-to-end parity tests cover local versus cloud workflows for open, preview/download fallback, analyze, status display, retry, and ownership negatives.
|
||||
|
||||
### Phase 14.2: Cross-Codebase Review and Cleanup (INSERTED)
|
||||
|
||||
**Goal:** Cross-reference the full codebase, remove duplicate logic, consolidate shared helpers/components/services, improve inefficient paths, delete dead code, and preserve behavior.
|
||||
**Requirements:** Cross-cutting quality gates
|
||||
**Depends on:** Phase 14.1
|
||||
**Plans:** 0 plans
|
||||
|
||||
Plans:
|
||||
|
||||
- [ ] TBD (run /gsd-plan-phase 14.2 to break down)
|
||||
|
||||
**Success Criteria:**
|
||||
|
||||
1. Backend routers, services, providers, and tasks are audited for duplicated helper logic, raw inline orchestration, repeated parsing/formatting, and inefficient query/cache paths.
|
||||
2. Frontend views, components, stores, and utilities are audited for duplicated browser logic, formatters, provider styling, tree behavior, and local/cloud branching.
|
||||
3. Shared module maps and non-negotiable rules in `AGENTS.md` are updated for any newly centralized helpers or components.
|
||||
4. Unused files, dead imports, stale tests, obsolete planning references, and unreachable code are removed in the same cleanup work.
|
||||
5. Full backend, frontend, security, audit, dependency, secret-scan, and rendered UI gates pass after behavior-preserving cleanup.
|
||||
|
||||
### Phase 15: Unified Smart Search
|
||||
|
||||
**Goal:** One search experience finds local and analyzed cloud documents by keywords, sentences, or semantic ideas and opens cloud results through on-demand hydration.
|
||||
|
||||
**Depends on:** Phase 14
|
||||
**Depends on:** Phase 14.2
|
||||
**Requirements:** SEARCH-01, SEARCH-02, SEARCH-03, SEARCH-04, SEARCH-05, SEARCH-06, SEARCH-07
|
||||
|
||||
**Success Criteria:**
|
||||
|
||||
+9
-9
@@ -5,10 +5,10 @@ milestone_name: Reimagining Cloud Storage integration
|
||||
current_phase: 14.1
|
||||
current_phase_name: cloud-local-file-parity-hardening
|
||||
status: not_planned
|
||||
stopped_at: Phase 14.1 context gathered
|
||||
last_updated: "2026-06-26T17:23:14.358Z"
|
||||
stopped_at: Phase 14.1 UI-SPEC approved
|
||||
last_updated: "2026-06-26T18:51:13.000Z"
|
||||
last_activity: 2026-06-26
|
||||
last_activity_desc: Inserted Phase 14.1 cloud/local parity hardening and Phase 14.2 cross-codebase review and cleanup
|
||||
last_activity_desc: Approved Phase 14.1 UI design contract
|
||||
progress:
|
||||
total_phases: 8
|
||||
completed_phases: 4
|
||||
@@ -20,15 +20,15 @@ progress:
|
||||
# Project State
|
||||
|
||||
**Project:** DocuVault
|
||||
**Status:** Phase 14.1 pending planning — Cloud/Local File Parity Hardening
|
||||
**Status:** Phase 14.1 UI-SPEC approved; pending planning — Cloud/Local File Parity Hardening
|
||||
**Last Updated:** 2026-06-26
|
||||
|
||||
## Current Position
|
||||
|
||||
Phase: 14.1 (cloud-local-file-parity-hardening) — NOT PLANNED
|
||||
Plan: 0 of 0
|
||||
Status: Phase 14 complete. Next: plan inserted Phase 14.1.
|
||||
Last activity: 2026-06-26 — Inserted Phase 14.1 cloud/local parity hardening and Phase 14.2 cross-codebase review and cleanup
|
||||
Status: Phase 14 complete. Phase 14.1 UI-SPEC approved. Next: plan inserted Phase 14.1.
|
||||
Last activity: 2026-06-26 — Approved Phase 14.1 UI design contract
|
||||
|
||||
## Phase Status
|
||||
|
||||
@@ -116,16 +116,16 @@ None.
|
||||
|
||||
## Session Continuity
|
||||
|
||||
**Stopped at:** Phase 14.1 context gathered
|
||||
**Stopped at:** Phase 14.1 UI-SPEC approved
|
||||
|
||||
_Updated at each phase transition._
|
||||
|
||||
| Field | Value |
|
||||
|---|---|
|
||||
| Last session | 2026-06-26T17:23:14.347Z |
|
||||
| Last session | 2026-06-26T18:51:13.000Z |
|
||||
| Next action | Plan 14.1 |
|
||||
| Pending decisions | None |
|
||||
| Resume file | .planning/phases/14.1-cloud-local-file-parity-hardening/14.1-CONTEXT.md |
|
||||
| Resume file | .planning/phases/14.1-cloud-local-file-parity-hardening/14.1-UI-SPEC.md |
|
||||
|
||||
## Decisions
|
||||
|
||||
|
||||
@@ -0,0 +1,189 @@
|
||||
---
|
||||
phase: 14.1-cloud-local-file-parity-hardening
|
||||
plan: 01
|
||||
type: execute
|
||||
wave: 1
|
||||
depends_on: []
|
||||
files_modified:
|
||||
- backend/tests/test_cloud_detail_parity.py
|
||||
- backend/tests/test_cloud_reanalyze_force.py
|
||||
- frontend/src/views/__tests__/CloudDetailParity.test.js
|
||||
- frontend/src/components/storage/__tests__/StorageBrowser.parity.test.js
|
||||
autonomous: true
|
||||
requirements: [CLOUD-02, ANALYZE-01, ANALYZE-05, ANALYZE-06, ANALYZE-07, CACHE-03, CACHE-05]
|
||||
must_haves:
|
||||
truths:
|
||||
- "A failing backend test asserts a cloud detail endpoint returns extracted_text, analysis_status, topics, and source metadata for the owner"
|
||||
- "A failing backend test asserts the cloud detail response excludes credentials_enc, object_key, and raw provider URLs"
|
||||
- "A failing backend test asserts a foreign user gets 404 and an admin is blocked from the cloud detail endpoint"
|
||||
- "A failing backend test asserts force re-analyze enqueues an already-current item while default enqueue still skips it"
|
||||
- "A failing frontend test asserts a cloud detail route renders the same core sections as local document detail"
|
||||
- "A failing frontend test asserts cloud and local file rows both navigate to a detail view and both show topic badges and analysis status in the same slot"
|
||||
artifacts:
|
||||
- path: "backend/tests/test_cloud_detail_parity.py"
|
||||
provides: "RED backend tests for cloud detail endpoint fields, auth/cache boundaries, and route-level parity"
|
||||
min_lines: 80
|
||||
- path: "backend/tests/test_cloud_reanalyze_force.py"
|
||||
provides: "RED backend tests for force re-analyze and single-item retry job creation"
|
||||
min_lines: 60
|
||||
- path: "frontend/src/views/__tests__/CloudDetailParity.test.js"
|
||||
provides: "RED frontend tests for cloud detail route + paired local/cloud detail section parity"
|
||||
min_lines: 60
|
||||
- path: "frontend/src/components/storage/__tests__/StorageBrowser.parity.test.js"
|
||||
provides: "RED frontend tests for paired local/cloud row parity (topic/status/action slots, Re-analyze copy)"
|
||||
min_lines: 60
|
||||
key_links:
|
||||
- from: "backend/tests/test_cloud_detail_parity.py"
|
||||
to: "backend/api/cloud/operations.py"
|
||||
via: "httpx AsyncClient GET against the new cloud detail route"
|
||||
pattern: "/detail|/item"
|
||||
- from: "frontend/src/views/__tests__/CloudDetailParity.test.js"
|
||||
to: "frontend/src/router/index.js"
|
||||
via: "router resolves a named cloud detail route"
|
||||
pattern: "cloud-file-detail"
|
||||
---
|
||||
|
||||
<objective>
|
||||
Create the RED (failing) test suites that pin down Phase 14.1 parity behavior before any implementation exists: an owner-scoped cloud detail endpoint with analysis fields and strict allowlist, force re-analyze + single-item retry semantics, a cloud detail route, and paired local/cloud parity for browser rows and detail surfaces.
|
||||
|
||||
Purpose: Lock the contract first so backend (Plan 02) and frontend (Plan 03/04) implementations have an executable target. Per CLAUDE.md Testing Protocol, every feature requires tests; these are written first so the Nyquist `<automated>` gates in later plans are real.
|
||||
Output: Two backend test files and two frontend test files that fail (or are skipped-pending) against the current codebase because the detail endpoint, route, force flag, and shared detail surface do not exist yet.
|
||||
</objective>
|
||||
|
||||
<execution_context>
|
||||
@$HOME/.claude/gsd-core/workflows/execute-plan.md
|
||||
@$HOME/.claude/gsd-core/templates/summary.md
|
||||
</execution_context>
|
||||
|
||||
<context>
|
||||
@.planning/PROJECT.md
|
||||
@.planning/ROADMAP.md
|
||||
@.planning/STATE.md
|
||||
@.planning/phases/14.1-cloud-local-file-parity-hardening/14.1-CONTEXT.md
|
||||
@.planning/phases/14.1-cloud-local-file-parity-hardening/14.1-RESEARCH.md
|
||||
@.planning/phases/14.1-cloud-local-file-parity-hardening/14.1-UI-SPEC.md
|
||||
@CLAUDE.md
|
||||
@backend/tests/test_cloud_security.py
|
||||
@backend/tests/test_cloud_analysis_contract.py
|
||||
@frontend/src/views/__tests__/CloudFolderView.test.js
|
||||
@frontend/src/views/__tests__/FileManagerView.test.js
|
||||
@frontend/src/components/storage/__tests__/StorageBrowser.capabilities.test.js
|
||||
</context>
|
||||
|
||||
<tasks>
|
||||
|
||||
<task type="auto" tdd="true">
|
||||
<name>Task 1: RED backend tests — cloud detail endpoint contract + force re-analyze + single-item retry</name>
|
||||
<read_first>
|
||||
- backend/tests/test_cloud_security.py (owner/admin/no-leak fixture + assertion patterns to reuse)
|
||||
- backend/tests/test_cloud_analysis_contract.py (analysis enqueue/idempotency fixture patterns)
|
||||
- backend/api/cloud/schemas.py (CloudItemOut, AnalysisEnqueueRequest — fields that DO and do NOT exist today)
|
||||
- backend/api/cloud/operations.py (existing open/preview/download route shapes and path patterns)
|
||||
- backend/services/cloud_analysis.py (enqueue_analysis_job and retry_job_item current signatures)
|
||||
- backend/db/models.py (CloudItem.extracted_text/analysis_status, CloudItemTopic, Topic)
|
||||
- .planning/phases/14.1-cloud-local-file-parity-hardening/14.1-CONTEXT.md (D-05, D-07, D-08, D-11, D-12, D-18)
|
||||
</read_first>
|
||||
<behavior>
|
||||
- Test: owner GET of the cloud detail endpoint returns 200 with extracted_text, analysis_status, semantic_index_status, topics (list of topic names), provider, location/parent metadata, and capability/unsupported reasons. (D-05)
|
||||
- Test: cloud detail response JSON contains no key matching credentials_enc, object_key, version_key, password, or a raw http(s) provider URL. (D-18, T-14-02)
|
||||
- Test: a different user GET of another user's cloud item detail returns 404; an admin token GET returns 403 (get_regular_user). (D-18)
|
||||
- Test: stale item detail still returns prior extracted_text and topics with analysis_status reflecting stale. (D-07)
|
||||
- Test: default enqueue of an already-indexed/current item yields already_current_count >= 1 and queued_count 0; the same enqueue with force=true yields queued_count >= 1 for that item. (D-11, ANALYZE-06)
|
||||
- Test: forced re-analyze does not mutate the provider (no rename/move/delete adapter call) and routes byte work through the existing analysis path. (ANALYZE-07)
|
||||
- Test: retry for a failed item with no surviving active job creates a single-item retry job (or returns a typed result that yields one queued item). (D-12, ANALYZE-05)
|
||||
</behavior>
|
||||
<action>
|
||||
Per D-20, these parity/security tests use mocked provider contracts for determinism (no live provider calls); preserve opt-in live tests only where existing patterns already support them — do not add new live-provider dependencies. Create backend/tests/test_cloud_detail_parity.py and backend/tests/test_cloud_reanalyze_force.py using the async httpx.AsyncClient + real-PostgreSQL fixtures already used in test_cloud_security.py and test_cloud_analysis_contract.py. Reuse existing fixtures for authenticated owner client, a second non-owner user, and an admin user; do not invent a new auth harness. Target the cloud detail endpoint at the path the implementation will add — use GET /api/cloud/connections/{connection_id}/items/{item_id:path}/detail (the path Plan 02 implements); assert against response_model CloudItemDetailOut field names: extracted_text, analysis_status, semantic_index_status, topics, provider, display_name, parent_ref/location, modified_at, size, content_type, capabilities, and unsupported_analysis_reason. For the no-leak assertion, serialize the full response body to a string and assert the absence of the literals enumerated by concept in the behavior block (credential field name, MinIO object-key field name, version-key field name, the substring https:// pointing at a provider host) — read these literal forbidden tokens from CLAUDE.md's allowlist-schema rule rather than hardcoding a code-fenced sample here. For force re-analyze, POST the analysis enqueue route with a body that includes force=true (the field Plan 02 adds to AnalysisEnqueueRequest) and assert queued_count increments for an item that default enqueue marks already_current. For single-item retry-with-no-job, drive retry through the route/service path Plan 02 defines (an owner-scoped retry that creates a one-item job when none exists) and assert exactly one queued item results. Where the endpoint/field does not yet exist, the test MUST fail with a clear assertion or a 404/422 — do NOT mark xfail/skip permanently; mark with pytest.mark.xfail(reason="14.1 detail endpoint pending Plan 02", strict=False) ONLY if needed to keep the suite green for unrelated CI, and remove the marker note in Plan 02. Prefer hard-failing tests. Do not place fenced code in this plan; write the tests directly in the files.
|
||||
</action>
|
||||
<verify>
|
||||
<automated>cd backend && python -m pytest tests/test_cloud_detail_parity.py tests/test_cloud_reanalyze_force.py -x 2>&1 | tail -20</automated>
|
||||
</verify>
|
||||
<acceptance_criteria>
|
||||
- backend/tests/test_cloud_detail_parity.py and backend/tests/test_cloud_reanalyze_force.py exist and import without collection errors (pytest collects them).
|
||||
- Running the two files shows failing/xfail assertions tied to the missing detail endpoint and missing force flag — NOT import or fixture errors.
|
||||
- grep finds the detail path token: `grep -F 'items/' backend/tests/test_cloud_detail_parity.py` returns at least one line referencing `/detail`.
|
||||
- grep finds force usage: `grep -c 'force' backend/tests/test_cloud_reanalyze_force.py` returns >= 1.
|
||||
- The no-leak test references the forbidden tokens (credentials_enc, object_key) by reading/asserting their absence: `grep -c 'object_key' backend/tests/test_cloud_detail_parity.py` returns >= 1.
|
||||
</acceptance_criteria>
|
||||
<done>Both backend test files exist, collect cleanly, and fail against current code for the documented missing behaviors.</done>
|
||||
</task>
|
||||
|
||||
<task type="auto" tdd="true">
|
||||
<name>Task 2: RED frontend tests — cloud detail route + paired local/cloud detail and row parity</name>
|
||||
<read_first>
|
||||
- frontend/src/views/__tests__/CloudFolderView.test.js (mocked store/api patterns, router stubs)
|
||||
- frontend/src/views/__tests__/FileManagerView.test.js (local row open + topic color reference)
|
||||
- frontend/src/components/storage/__tests__/StorageBrowser.capabilities.test.js (disabled-capability assertions, mount helpers)
|
||||
- frontend/src/router/index.js (existing /document/:id and /cloud routes; no cloud-file-detail yet)
|
||||
- frontend/src/views/DocumentView.vue (local detail sections: header, Topics card, Extracted Text card, Re-classify copy)
|
||||
- frontend/src/components/storage/StorageBrowser.vue (file row: file-open emit, topics in name cell, analyze-file slot)
|
||||
- .planning/phases/14.1-cloud-local-file-parity-hardening/14.1-UI-SPEC.md (Detail Layout Contract, Browser Parity Contract, Copywriting Contract)
|
||||
</read_first>
|
||||
<behavior>
|
||||
- Test: a named route cloud-file-detail exists under /cloud and resolves a cloud detail view component. (UI-SPEC Route Contract)
|
||||
- Test: mounting the cloud detail view with a not-yet-analyzed CloudItem renders an Analyze action and empty-state copy "No analysis yet". (UI-SPEC Detail State Contract; D-02)
|
||||
- Test: mounting the cloud detail view with an analyzed CloudItem renders extracted text, topic badges, analysis status, and a Re-analyze action — same section order as DocumentView. (D-05, D-19)
|
||||
- Test: paired assertion — both local document detail and cloud detail render the section order Header → Status/Source → Topics → Extracted Text, and the analysis action slot holds Analyze/Re-analyze/Retry by state. (D-17, Detail Layout Contract)
|
||||
- Test: clicking a cloud file row navigates to cloud-file-detail (router push), NOT a direct preview/download; clicking a local file row navigates to /document/:id. (UI-SPEC Browser Parity; common pitfall: no auto-download)
|
||||
- Test: paired StorageBrowser row assertion — local and cloud file rows both render topic badges beneath the filename and an analysis-status indicator in the same relative slot. (D-06)
|
||||
- Test: visible copy uses "Re-analyze" and no rendered output contains "Re-classify". (D-09, Copywriting Contract)
|
||||
</behavior>
|
||||
<action>
|
||||
Create frontend/src/views/__tests__/CloudDetailParity.test.js and frontend/src/components/storage/__tests__/StorageBrowser.parity.test.js using Vitest + @vue/test-utils mount patterns already in CloudFolderView.test.js and StorageBrowser.capabilities.test.js. Mock the cloud API barrel (api.* from frontend/src/api/cloud.js) and the cloudConnections store the same way existing tests do (vi.mock on the barrel so interception works — see Phase 13 P07 decision). Reference the cloud detail view at the component path Plan 03 creates (frontend/src/views/CloudDetailView.vue) and the shared detail surface (frontend/src/components/storage/DocumentDetailSurface.vue) — import them and let the test fail with a module-not-found or render assertion until Plan 03 lands. Assert the named route cloud-file-detail against the router by importing frontend/src/router/index.js and checking router.getRoutes() / resolve({ name: 'cloud-file-detail' }) succeeds. For row parity, mount StorageBrowser twice — once in local mode (no capabilities prop) with a fixture file carrying topics + analysis_status, once in cloud mode with the same shaped fixture — and assert both render TopicBadge children and an analysis-status element in the name-cell slot. For the Re-classify regression, assert the rendered text of the cloud detail surface contains "Re-analyze" and does not contain "Re-classify". Prefer hard-failing tests over skips; if a permanent skip is unavoidable for CI greenness, use it.skip with a reason string that Plan 03/04 removes. No fenced code in this plan — write directly into the test files.
|
||||
</action>
|
||||
<verify>
|
||||
<automated>cd frontend && npx vitest run src/views/__tests__/CloudDetailParity.test.js src/components/storage/__tests__/StorageBrowser.parity.test.js 2>&1 | tail -25</automated>
|
||||
</verify>
|
||||
<acceptance_criteria>
|
||||
- Both new test files exist and are collected by Vitest (no syntax/parse errors).
|
||||
- The route assertion references the named route: `grep -c 'cloud-file-detail' frontend/src/views/__tests__/CloudDetailParity.test.js` returns >= 1.
|
||||
- The Re-classify regression is present: `grep -c 'Re-analyze' frontend/src/views/__tests__/CloudDetailParity.test.js` returns >= 1.
|
||||
- The row parity test mounts StorageBrowser in both modes: `grep -c 'StorageBrowser' frontend/src/components/storage/__tests__/StorageBrowser.parity.test.js` returns >= 1.
|
||||
- Running the suite shows failing assertions tied to the missing CloudDetailView / DocumentDetailSurface / cloud-file-detail route — not unrelated infrastructure failures.
|
||||
</acceptance_criteria>
|
||||
<done>Both frontend test files exist, are collected by Vitest, and fail against current code for the documented missing parity behaviors.</done>
|
||||
</task>
|
||||
|
||||
</tasks>
|
||||
|
||||
<threat_model>
|
||||
## Trust Boundaries
|
||||
|
||||
| Boundary | Description |
|
||||
|----------|-------------|
|
||||
| browser → cloud detail API | Untrusted connection_id/provider_item_id cross here; tests assert owner scoping and no leakage |
|
||||
| API response → browser | Response must never carry credentials_enc, object_key, provider URLs |
|
||||
|
||||
## STRIDE Threat Register
|
||||
|
||||
| Threat ID | Category | Component | Disposition | Mitigation Plan |
|
||||
|-----------|----------|-----------|-------------|-----------------|
|
||||
| T-14.1-01 | Information Disclosure | cloud detail response schema | mitigate | RED test asserts response excludes credentials_enc/object_key/version_key/provider URL (verified by Plan 02 schema) |
|
||||
| T-14.1-02 | Elevation of Privilege | cloud detail + force re-analyze auth | mitigate | RED test asserts foreign-user 404 and admin 403 on detail and force enqueue |
|
||||
| T-14.1-SC | Tampering | npm/pip installs | accept | No package installs this plan (RESEARCH Package Legitimacy Audit: no new packages) |
|
||||
</threat_model>
|
||||
|
||||
<verification>
|
||||
- Backend: `cd backend && python -m pytest tests/test_cloud_detail_parity.py tests/test_cloud_reanalyze_force.py` collects and fails on documented missing behaviors only.
|
||||
- Frontend: `cd frontend && npx vitest run src/views/__tests__/CloudDetailParity.test.js src/components/storage/__tests__/StorageBrowser.parity.test.js` collects and fails on documented missing behaviors only.
|
||||
- No existing passing tests are broken by adding these files (they are new files; run `cd backend && python -m pytest -q 2>&1 | tail -5` to confirm collection still works).
|
||||
</verification>
|
||||
|
||||
<success_criteria>
|
||||
- Four new test files exist (2 backend, 2 frontend).
|
||||
- Each file fails for the intended missing-behavior reasons, not for fixture/import/infrastructure errors.
|
||||
- Forbidden-token absence, force flag, named route, and Re-analyze copy are all referenced in tests.
|
||||
</success_criteria>
|
||||
|
||||
<artifacts_produced>
|
||||
## Artifacts this phase produces (Plan 01)
|
||||
|
||||
- File: `backend/tests/test_cloud_detail_parity.py` — RED tests for cloud detail endpoint fields, no-leak allowlist, owner/admin negatives, stale-data preservation.
|
||||
- File: `backend/tests/test_cloud_reanalyze_force.py` — RED tests for force re-analyze (force=true) and single-item retry job creation.
|
||||
- File: `frontend/src/views/__tests__/CloudDetailParity.test.js` — RED tests for cloud-file-detail route, detail section parity, row-click navigation, Re-analyze copy.
|
||||
- File: `frontend/src/components/storage/__tests__/StorageBrowser.parity.test.js` — RED tests for paired local/cloud row parity (topics, status, action slots).
|
||||
</artifacts_produced>
|
||||
|
||||
<output>
|
||||
Create `.planning/phases/14.1-cloud-local-file-parity-hardening/14.1-01-SUMMARY.md` when done
|
||||
</output>
|
||||
@@ -0,0 +1,184 @@
|
||||
---
|
||||
phase: 14.1-cloud-local-file-parity-hardening
|
||||
plan: 02
|
||||
type: execute
|
||||
wave: 2
|
||||
depends_on: [14.1-01]
|
||||
files_modified:
|
||||
- backend/api/cloud/schemas.py
|
||||
- backend/services/cloud_items.py
|
||||
- backend/api/cloud/operations.py
|
||||
- backend/services/cloud_analysis.py
|
||||
- backend/api/cloud/analysis.py
|
||||
autonomous: true
|
||||
requirements: [CLOUD-02, ANALYZE-01, ANALYZE-05, ANALYZE-06, ANALYZE-07, CACHE-03, CACHE-05]
|
||||
must_haves:
|
||||
truths:
|
||||
- "An owner can fetch a cloud item detail with extracted text, analysis status, topics, and source metadata through an authorized endpoint"
|
||||
- "The cloud detail response never includes credentials_enc, object_key, version_key, or raw provider URLs"
|
||||
- "A foreign user gets 404 and an admin is blocked from cloud detail"
|
||||
- "Default analysis enqueue still skips already-current items; force=true re-queues them without mutating the provider"
|
||||
- "Failed-item retry with no surviving active job creates a single-item retry job through the authorized analysis path"
|
||||
- "Cloud detail resolution downloads zero provider bytes"
|
||||
artifacts:
|
||||
- path: "backend/api/cloud/schemas.py"
|
||||
provides: "CloudItemDetailOut schema (allowlist, analysis fields, no object_key/credentials_enc) and force field on AnalysisEnqueueRequest"
|
||||
contains: "class CloudItemDetailOut"
|
||||
- path: "backend/services/cloud_items.py"
|
||||
provides: "resolve_owned_cloud_item_detail — owner-scoped detail + topic resolution, raises ValueError/domain exception"
|
||||
contains: "resolve_owned_cloud_item_detail"
|
||||
- path: "backend/api/cloud/operations.py"
|
||||
provides: "GET cloud item detail route returning CloudItemDetailOut, metadata-only (no byte hydration)"
|
||||
contains: "/detail"
|
||||
- path: "backend/services/cloud_analysis.py"
|
||||
provides: "force re-analyze bypass of already_current and a single-item retry-job creation helper"
|
||||
contains: "force"
|
||||
- path: "backend/api/cloud/analysis.py"
|
||||
provides: "enqueue route passes force through; retry route falls back to single-item job creation"
|
||||
contains: "force"
|
||||
key_links:
|
||||
- from: "backend/api/cloud/operations.py"
|
||||
to: "backend/services/cloud_items.py"
|
||||
via: "detail route calls resolve_owned_cloud_item_detail"
|
||||
pattern: "resolve_owned_cloud_item_detail"
|
||||
- from: "backend/api/cloud/analysis.py"
|
||||
to: "backend/services/cloud_analysis.py"
|
||||
via: "enqueue route forwards force= to enqueue_analysis_job"
|
||||
pattern: "force="
|
||||
---
|
||||
|
||||
<objective>
|
||||
Add the backend surface that makes cloud files behave like local documents: an owner-scoped cloud item detail endpoint that returns extracted text, analysis status, topics, and subtle source metadata through a strict credential-free schema; a force re-analyze flag that lets a user re-queue an already-current cloud item; and a single-item retry-job creation path so a failed item can be retried even when no active job survives.
|
||||
|
||||
Purpose: Implements D-01..D-08, D-11, D-12, D-15, D-16 backend contracts so the frontend (Plan 03/04) can render parity without forking. Satisfies CLOUD-02 (authorized open/preview/view), ANALYZE-05 (retry), ANALYZE-06 (idempotency with explicit force override), ANALYZE-07 (no provider mutation), CACHE-03/CACHE-05 (bytes only via cache, owner-scoped).
|
||||
Output: CloudItemDetailOut schema, resolve_owned_cloud_item_detail service helper, GET detail route, force-aware enqueue, single-item retry-job path — all covered by Plan 01 tests turning GREEN.
|
||||
</objective>
|
||||
|
||||
<execution_context>
|
||||
@$HOME/.claude/gsd-core/workflows/execute-plan.md
|
||||
@$HOME/.claude/gsd-core/templates/summary.md
|
||||
</execution_context>
|
||||
|
||||
<context>
|
||||
@.planning/PROJECT.md
|
||||
@.planning/ROADMAP.md
|
||||
@.planning/STATE.md
|
||||
@.planning/phases/14.1-cloud-local-file-parity-hardening/14.1-CONTEXT.md
|
||||
@.planning/phases/14.1-cloud-local-file-parity-hardening/14.1-RESEARCH.md
|
||||
@CLAUDE.md
|
||||
@backend/api/cloud/schemas.py
|
||||
@backend/services/cloud_items.py
|
||||
@backend/api/cloud/operations.py
|
||||
@backend/services/cloud_analysis.py
|
||||
@backend/api/cloud/analysis.py
|
||||
@backend/db/models.py
|
||||
@backend/tests/test_cloud_detail_parity.py
|
||||
@backend/tests/test_cloud_reanalyze_force.py
|
||||
</context>
|
||||
|
||||
<tasks>
|
||||
|
||||
<task type="auto" tdd="true">
|
||||
<name>Task 1: CloudItemDetailOut schema + resolve_owned_cloud_item_detail service + GET detail route</name>
|
||||
<read_first>
|
||||
- backend/api/cloud/schemas.py (CloudItemOut, CloudCapabilityOut, CacheStatusOut allowlist style — model the new schema on these)
|
||||
- backend/services/cloud_items.py (resolve_owned_connection, list_cloud_children, ListingResult — reuse owner-scoping pattern; service raises ValueError/domain exception, never HTTPException)
|
||||
- backend/api/cloud/operations.py (open/preview/download routes, _resolve_and_get_adapter, get_regular_user dependency, parse_uuid usage, JSONResponse vs HTTPException convention)
|
||||
- backend/db/models.py (CloudItem fields: extracted_text, analysis_status, semantic_index_status, provider_size, content_type, modified_at, parent_ref, path_snapshot; CloudItemTopic ↔ Topic join; CloudConnection.provider/display_name)
|
||||
- backend/tests/test_cloud_detail_parity.py (the exact field names and forbidden tokens the route must satisfy)
|
||||
- .planning/phases/14.1-cloud-local-file-parity-hardening/14.1-CONTEXT.md (D-04, D-05, D-07, D-08, D-15, D-16, D-18)
|
||||
</read_first>
|
||||
<action>
|
||||
In backend/api/cloud/schemas.py add class CloudItemDetailOut(BaseModel) as an explicit allowlist modeled on CloudItemOut + CacheStatusOut. Fields (and ONLY these — no credentials_enc, object_key, version_key, provider URL, token, fingerprint): id (DocuVault UUID str), provider_item_id, name, kind, parent_ref, content_type, size, modified_at, etag, provider (connection provider string), display_name (connection display name), location (human path_snapshot or parent_ref, no provider URL), analysis_status, semantic_index_status, extracted_text (Optional[str]), topics (list[str] of topic names), capabilities (dict[str, CloudCapabilityOut]), unsupported_analysis_reason (Optional[str], populated from capabilities when analyze is unsupported per D-16), is_stale (bool derived from analysis_status == "stale" per D-07). Document at class top that object_key/credentials_enc are absent by design (mirror the T-14-02 docstring pattern). In backend/services/cloud_items.py add async def resolve_owned_cloud_item_detail(session, *, user_id, connection_id, provider_item_id) that: resolves the connection via resolve_owned_connection (owner check), selects the CloudItem by (connection_id, provider_item_id) scoped to user_id, raises a domain exception / ValueError when not found (router maps to 404), loads topic names via a CloudItemTopic→Topic join, and returns a plain dataclass/dict the router maps into CloudItemDetailOut. This helper performs metadata-only DB reads — it MUST NOT call adapter.get_object or hydrate_and_cache_bytes (T-14-04 / CACHE-03). In backend/api/cloud/operations.py register GET /connections/{connection_id}/items/{item_id:path}/detail with response_model=CloudItemDetailOut and dependency get_regular_user (admin blocked); parse connection_id with parse_uuid; call resolve_owned_cloud_item_detail; on the domain not-found exception raise HTTPException(404) (router layer translates, per CLAUDE.md service-vs-router rule); never decrypt or expose credentials. Do not add a parallel detail router file — keep it on the existing operations router so it shares the /api/cloud prefix.
|
||||
</action>
|
||||
<verify>
|
||||
<automated>cd backend && python -m pytest tests/test_cloud_detail_parity.py -x 2>&1 | tail -20</automated>
|
||||
</verify>
|
||||
<acceptance_criteria>
|
||||
- `grep -c 'class CloudItemDetailOut' backend/api/cloud/schemas.py` returns 1.
|
||||
- CloudItemDetailOut has no forbidden fields: `grep -E 'object_key|credentials_enc|version_key' backend/api/cloud/schemas.py | grep -A0 -i detail` is empty within the class body (manual confirm the class block excludes them).
|
||||
- `grep -c 'resolve_owned_cloud_item_detail' backend/services/cloud_items.py` returns >= 1 (definition present).
|
||||
- The detail route exists: `grep -F '/detail' backend/api/cloud/operations.py` returns a route line with `response_model=CloudItemDetailOut`.
|
||||
- The detail service does not hydrate bytes: `grep -n 'get_object\|hydrate_and_cache_bytes' backend/services/cloud_items.py` shows no new call inside resolve_owned_cloud_item_detail.
|
||||
- test_cloud_detail_parity.py passes: owner gets fields, foreign user 404, admin 403, response has no forbidden tokens, stale preserves extracted_text/topics.
|
||||
</acceptance_criteria>
|
||||
<done>Cloud detail endpoint returns analysis fields + source metadata for the owner through a credential-free schema, blocks foreign user/admin, and downloads no bytes; Plan 01 detail tests pass.</done>
|
||||
</task>
|
||||
|
||||
<task type="auto" tdd="true">
|
||||
<name>Task 2: Force re-analyze flag + single-item retry-job creation</name>
|
||||
<read_first>
|
||||
- backend/services/cloud_analysis.py (enqueue_analysis_job signature, already_current branch ~line 546-660, retry_job_item ~line 1049, _check_already_current, EnqueueResult)
|
||||
- backend/api/cloud/schemas.py (AnalysisEnqueueRequest — add force; AnalysisEnqueueOut)
|
||||
- backend/api/cloud/analysis.py (enqueue_job route ~line 137, retry route, _build_job_out, AnalysisControlOut)
|
||||
- backend/tests/test_cloud_reanalyze_force.py (exact force=true and single-item retry assertions)
|
||||
- .planning/phases/14.1-cloud-local-file-parity-hardening/14.1-CONTEXT.md (D-11, D-12)
|
||||
- .planning/phases/14.1-cloud-local-file-parity-hardening/14.1-RESEARCH.md (Forced Re-Analyze Pattern section)
|
||||
</read_first>
|
||||
<action>
|
||||
Add force: bool = Field(default=False) to AnalysisEnqueueRequest in backend/api/cloud/schemas.py (default preserves existing idempotency). Extend enqueue_analysis_job in backend/services/cloud_analysis.py to accept force: bool = False; when force is True, skip the already_current branch so supported items are created as queued job items even if their version_key matches an indexed item — the already_current check becomes (already_current and not force). Forced enqueue must still: validate owner/connection/item and unsupported type (unsupported items stay unsupported), compute version_key/fingerprint normally, and create queued items only — it MUST NOT call any provider mutation (no rename/move/delete) and MUST NOT bypass the cache lifecycle; processing remains process_cloud_analysis_item + hydrate_and_cache_bytes (ANALYZE-07, CACHE-03). Wire force through the enqueue_job route in backend/api/cloud/analysis.py: pass body.force into enqueue_analysis_job(...). For D-12, add an owner-scoped single-item retry path: when a failed item cannot be retried within an existing job (no surviving active job, or retry_job_item raises AnalysisJobNotFound for that cloud_item_id), create a one-item analysis job for that cloud_item_id via the existing enqueue_analysis_job(scope="file", provider_item_ids=[provider_item_id], force=True) so a fresh queued item is produced through the authorized path. Implement this fallback either in the retry route handler (catch the not-found/invalid-state domain exception and create the single-item job) or as a thin service helper (e.g. retry_or_create_single_item_job) in cloud_analysis.py — choose the service helper if both the route and tests need it. Keep all aggregate counters consistent and return the existing typed result schemas (AnalysisControlOut / AnalysisEnqueueOut). Do not raise HTTPException from the service layer — raise domain exceptions and translate in the route.
|
||||
</action>
|
||||
<verify>
|
||||
<automated>cd backend && python -m pytest tests/test_cloud_reanalyze_force.py tests/test_cloud_analysis_contract.py -x 2>&1 | tail -20</automated>
|
||||
</verify>
|
||||
<acceptance_criteria>
|
||||
- `grep -c 'force' backend/api/cloud/schemas.py` shows force added to AnalysisEnqueueRequest (>= 1 occurrence near the class).
|
||||
- enqueue_analysis_job accepts force: `grep -n 'def enqueue_analysis_job' backend/services/cloud_analysis.py` and the signature/body reference force.
|
||||
- The already_current branch respects force: `grep -n 'already_current and not force\|not force' backend/services/cloud_analysis.py` returns >= 1.
|
||||
- The enqueue route forwards force: `grep -F 'force=' backend/api/cloud/analysis.py` returns >= 1 (or body.force passed positionally is visible).
|
||||
- Single-item retry fallback exists: `grep -nE 'retry_or_create_single_item_job|scope="file"|single' backend/services/cloud_analysis.py backend/api/cloud/analysis.py` shows the fallback path.
|
||||
- test_cloud_reanalyze_force.py passes: default skips already-current, force re-queues, no provider mutation, single-item retry yields one queued item. test_cloud_analysis_contract.py still passes (no regression in default idempotency).
|
||||
</acceptance_criteria>
|
||||
<done>force=true re-queues already-current items without provider mutation, default enqueue is unchanged, and failed items can be retried via a single-item job when no active job exists; Plan 01 force/retry tests pass with no idempotency regression.</done>
|
||||
</task>
|
||||
|
||||
</tasks>
|
||||
|
||||
<threat_model>
|
||||
## Trust Boundaries
|
||||
|
||||
| Boundary | Description |
|
||||
|----------|-------------|
|
||||
| browser → GET detail | connection_id/provider_item_id untrusted; must resolve under current_user.id only |
|
||||
| browser → POST enqueue (force) | force is a user-driven flag; must not enable cross-user enqueue or provider mutation |
|
||||
| service → DB | metadata-only reads; no byte hydration during detail |
|
||||
|
||||
## STRIDE Threat Register
|
||||
|
||||
| Threat ID | Category | Component | Disposition | Mitigation Plan |
|
||||
|-----------|----------|-----------|-------------|-----------------|
|
||||
| T-14.1-03 | Information Disclosure | CloudItemDetailOut | mitigate | Allowlist schema excludes credentials_enc/object_key/version_key/provider URL; Plan 01 no-leak test enforces |
|
||||
| T-14.1-04 | Elevation of Privilege | detail route + force enqueue | mitigate | get_regular_user (admin 403) + resolve under current_user.id (foreign 404); tests enforce |
|
||||
| T-14.1-05 | Tampering | forced re-analyze | mitigate | Force only re-queues analysis; processing path unchanged (no rename/move/delete adapter call); ANALYZE-07 test enforces |
|
||||
| T-14.1-06 | Information Disclosure | detail byte access | mitigate | Detail resolution is metadata-only; never calls get_object/hydrate_and_cache_bytes (CACHE-03) |
|
||||
| T-14.1-SC | Tampering | npm/pip installs | accept | No package installs (RESEARCH Package Legitimacy Audit: none) |
|
||||
</threat_model>
|
||||
|
||||
<verification>
|
||||
- `cd backend && python -m pytest tests/test_cloud_detail_parity.py tests/test_cloud_reanalyze_force.py -x` passes.
|
||||
- `cd backend && python -m pytest tests/test_cloud_analysis_contract.py tests/test_cloud_security.py -x` passes (no regression).
|
||||
- `cd backend && python -m pytest -q 2>&1 | tail -5` — full backend suite passes.
|
||||
</verification>
|
||||
|
||||
<success_criteria>
|
||||
- CloudItemDetailOut exists as a credential-free allowlist with analysis fields + source metadata.
|
||||
- resolve_owned_cloud_item_detail resolves owner-scoped detail + topics with zero byte hydration.
|
||||
- GET detail route is owner-scoped (foreign 404, admin 403).
|
||||
- force=true re-queues already-current items; default enqueue unchanged; no provider mutation.
|
||||
- Single-item retry job creation works when no active job survives.
|
||||
</success_criteria>
|
||||
|
||||
<artifacts_produced>
|
||||
## Artifacts this phase produces (Plan 02)
|
||||
|
||||
- Class: `CloudItemDetailOut` (backend/api/cloud/schemas.py) — credential-free cloud detail response with extracted_text, analysis_status, semantic_index_status, topics, provider, display_name, location, capabilities, unsupported_analysis_reason, is_stale.
|
||||
- Field: `force: bool` on `AnalysisEnqueueRequest` (backend/api/cloud/schemas.py).
|
||||
- Function: `resolve_owned_cloud_item_detail(session, *, user_id, connection_id, provider_item_id)` (backend/services/cloud_items.py).
|
||||
- Route: `GET /api/cloud/connections/{connection_id}/items/{item_id:path}/detail` → CloudItemDetailOut (backend/api/cloud/operations.py).
|
||||
- Parameter: `force: bool = False` on `enqueue_analysis_job` (backend/services/cloud_analysis.py).
|
||||
- Function/path: single-item retry-job creation fallback (e.g. `retry_or_create_single_item_job`) in backend/services/cloud_analysis.py and/or the retry route in backend/api/cloud/analysis.py.
|
||||
</artifacts_produced>
|
||||
|
||||
<output>
|
||||
Create `.planning/phases/14.1-cloud-local-file-parity-hardening/14.1-02-SUMMARY.md` when done
|
||||
</output>
|
||||
@@ -0,0 +1,187 @@
|
||||
---
|
||||
phase: 14.1-cloud-local-file-parity-hardening
|
||||
plan: 03
|
||||
type: execute
|
||||
wave: 3
|
||||
depends_on: [14.1-01, 14.1-02]
|
||||
files_modified:
|
||||
- frontend/src/components/storage/DocumentDetailSurface.vue
|
||||
- frontend/src/views/DocumentView.vue
|
||||
- frontend/src/views/CloudDetailView.vue
|
||||
- frontend/src/router/index.js
|
||||
- frontend/src/api/cloud.js
|
||||
- frontend/src/stores/cloudConnections.js
|
||||
autonomous: true
|
||||
requirements: [CLOUD-02, ANALYZE-01, ANALYZE-05, ANALYZE-06, CACHE-03]
|
||||
must_haves:
|
||||
truths:
|
||||
- "A shared DocumentDetailSurface renders header, source metadata, status, topics, and extracted text for both local and cloud detail"
|
||||
- "Local DocumentView and cloud CloudDetailView are thin data providers that feed the shared surface and handle its events"
|
||||
- "A named cloud-file-detail route opens a cloud detail view from a connection id and opaque provider item id"
|
||||
- "The cloud detail view shows Analyze before analysis and Re-analyze after, with confirmation for forcing a current file"
|
||||
- "Preview unavailable keeps Download active and never auto-downloads"
|
||||
- "Visible copy says Re-analyze everywhere; no rendered Re-classify remains"
|
||||
artifacts:
|
||||
- path: "frontend/src/components/storage/DocumentDetailSurface.vue"
|
||||
provides: "Shared detail surface (header, source metadata, status/stale, topics, extracted text, action slot)"
|
||||
min_lines: 80
|
||||
- path: "frontend/src/views/CloudDetailView.vue"
|
||||
provides: "Cloud detail data-provider view backed by CloudItem detail endpoint"
|
||||
min_lines: 60
|
||||
- path: "frontend/src/router/index.js"
|
||||
provides: "Named cloud-file-detail route under /cloud"
|
||||
contains: "cloud-file-detail"
|
||||
- path: "frontend/src/api/cloud.js"
|
||||
provides: "getCloudItemDetail API client method"
|
||||
contains: "getCloudItemDetail"
|
||||
- path: "frontend/src/stores/cloudConnections.js"
|
||||
provides: "reanalyze/force enqueue + detail fetch wiring; single translateAnalysisStatus source"
|
||||
contains: "translateAnalysisStatus"
|
||||
key_links:
|
||||
- from: "frontend/src/views/CloudDetailView.vue"
|
||||
to: "frontend/src/api/cloud.js"
|
||||
via: "calls getCloudItemDetail(connectionId, itemId)"
|
||||
pattern: "getCloudItemDetail"
|
||||
- from: "frontend/src/views/DocumentView.vue"
|
||||
to: "frontend/src/components/storage/DocumentDetailSurface.vue"
|
||||
via: "renders the shared surface with local data + handlers"
|
||||
pattern: "DocumentDetailSurface"
|
||||
- from: "frontend/src/router/index.js"
|
||||
to: "frontend/src/views/CloudDetailView.vue"
|
||||
via: "cloud-file-detail route component"
|
||||
pattern: "CloudDetailView"
|
||||
---
|
||||
|
||||
<objective>
|
||||
Extract a shared document detail surface from DocumentView.vue and build a cloud detail route/view backed by the CloudItem detail endpoint, so local and cloud files render the same detail layout, status, topics, extracted text, and action slot without forking. Rename visible Re-classify to Re-analyze, add force re-analyze with confirmation, and ensure unsupported preview never auto-downloads.
|
||||
|
||||
Purpose: Implements D-01..D-11, D-13, D-14, D-19 frontend contracts and the UI-SPEC Detail Layout / Detail State / Preview-Unsupported / Re-Analyze contracts. Satisfies CLOUD-02 (authorized open/preview/view parity), ANALYZE-01/05/06 (analyze/re-analyze/retry/force), CACHE-03 (bytes only via authorized handlers).
|
||||
Output: DocumentDetailSurface.vue shared component; DocumentView.vue refactored to use it; CloudDetailView.vue + cloud-file-detail route; getCloudItemDetail API method; store wiring for force re-analyze and detail fetch.
|
||||
</objective>
|
||||
|
||||
<execution_context>
|
||||
@$HOME/.claude/gsd-core/workflows/execute-plan.md
|
||||
@$HOME/.claude/gsd-core/templates/summary.md
|
||||
</execution_context>
|
||||
|
||||
<context>
|
||||
@.planning/PROJECT.md
|
||||
@.planning/ROADMAP.md
|
||||
@.planning/STATE.md
|
||||
@.planning/phases/14.1-cloud-local-file-parity-hardening/14.1-CONTEXT.md
|
||||
@.planning/phases/14.1-cloud-local-file-parity-hardening/14.1-RESEARCH.md
|
||||
@.planning/phases/14.1-cloud-local-file-parity-hardening/14.1-UI-SPEC.md
|
||||
@CLAUDE.md
|
||||
@frontend/src/views/DocumentView.vue
|
||||
@frontend/src/views/CloudFolderView.vue
|
||||
@frontend/src/router/index.js
|
||||
@frontend/src/api/cloud.js
|
||||
@frontend/src/stores/cloudConnections.js
|
||||
@frontend/src/utils/formatters.js
|
||||
@frontend/src/views/__tests__/CloudDetailParity.test.js
|
||||
</context>
|
||||
|
||||
<tasks>
|
||||
|
||||
<task type="auto" tdd="true">
|
||||
<name>Task 1: Extract DocumentDetailSurface.vue and refactor DocumentView.vue to use it (Re-analyze copy)</name>
|
||||
<read_first>
|
||||
- frontend/src/views/DocumentView.vue (full file — header block, Topics card with Re-classify button, Extracted Text card, reclassify()/suggestTopics() methods, preview modal)
|
||||
- frontend/src/components/topics/TopicBadge.vue (topic badge presentational component reused by the surface)
|
||||
- frontend/src/utils/formatters.js (formatDate, formatSize, providerColor, providerBg, providerLabel — use these, do not redefine)
|
||||
- .planning/phases/14.1-cloud-local-file-parity-hardening/14.1-UI-SPEC.md (Detail Layout Contract section order; Copywriting Contract; Re-Analyze And Retry Contract)
|
||||
- .planning/phases/14.1-cloud-local-file-parity-hardening/14.1-CONTEXT.md (D-04, D-05, D-08, D-09, D-10)
|
||||
- frontend/src/views/__tests__/CloudDetailParity.test.js (paired detail section-order assertions the surface must satisfy)
|
||||
</read_first>
|
||||
<action>
|
||||
Create frontend/src/components/storage/DocumentDetailSurface.vue as a presentational smart component owning the detail layout for BOTH local and cloud detail. Per the UI-SPEC Detail Layout Contract, the section order is: (1) Back navigation slot, (2) Header block — title (24px semibold, break-all wrap), metadata line (date/size/type, 12-14px muted), subtle source metadata line/chips for cloud (provider chip via providerColor/providerBg + location; absent or minimal for local), and a primary action cluster, (3) Status and source notice area (current/stale/working badges using green/amber/blue per UI-SPEC), (4) Topics section (TopicBadge list + empty copy "No topics assigned yet."), (5) Extracted text section (pre block), (6) secondary controls / inline error+help copy. Define props: title, metadataLine, source (provider/location/isStale/statusLabel), topics (array), analysisStatus, extractedText, previewState ({ supported, reason }), downloadState ({ supported }), analysisAction ({ kind: 'analyze'|'reanalyze'|'retry', busy }), and optional slots for delete/share/suggest controls so local DocumentView can inject its extra buttons. Define emits: preview, download, reanalyze, retry-analysis, analyze. The single analysis action slot swaps Analyze/Re-analyze/Retry by analysisAction.kind (never accumulates buttons — UI-SPEC Re-Analyze contract). Use the Copywriting Contract literals: primary CTA "Analyze file" / "Preview file" / "Open file", empty heading "No analysis yet", empty body "Analyze this file to extract text and topics.". Use ONLY formatters from utils/formatters.js. Then refactor frontend/src/views/DocumentView.vue to render DocumentDetailSurface, feeding local document data and wiring its existing methods: pass reclassify() as the reanalyze handler, change the VISIBLE button label from "Re-classify" to "Re-analyze" (keep the internal classifyDocument API call name unchanged per D-09 / Codex discretion), keep suggestTopics and delete via the surface's secondary slots. Do not duplicate the card/grid markup in DocumentView after refactor — it becomes a data provider. Per CLAUDE.md no-dead-code rule, remove any now-unused local markup blocks in the same edit.
|
||||
</action>
|
||||
<verify>
|
||||
<automated>cd frontend && npx vitest run src/views/__tests__/CloudDetailParity.test.js 2>&1 | tail -20 && grep -rc 'Re-classify' frontend/src/views/DocumentView.vue frontend/src/components/storage/DocumentDetailSurface.vue</automated>
|
||||
</verify>
|
||||
<acceptance_criteria>
|
||||
- `frontend/src/components/storage/DocumentDetailSurface.vue` exists and imports TopicBadge and formatters from utils/formatters.js (no local formatDate/formatSize/providerColor definitions): `grep -c "from '../../utils/formatters" frontend/src/components/storage/DocumentDetailSurface.vue` returns >= 1.
|
||||
- DocumentView renders the shared surface: `grep -c 'DocumentDetailSurface' frontend/src/views/DocumentView.vue` returns >= 1.
|
||||
- No rendered Re-classify in either file: `grep -c 'Re-classify' frontend/src/views/DocumentView.vue frontend/src/components/storage/DocumentDetailSurface.vue` returns 0.
|
||||
- Visible Re-analyze present: `grep -c 'Re-analyze' frontend/src/components/storage/DocumentDetailSurface.vue` returns >= 1.
|
||||
- The surface's analysis action region uses a single state-swapped slot (no two simultaneous Analyze+Re-analyze buttons) — confirm by reading the template region.
|
||||
- The paired detail section-order assertions in CloudDetailParity.test.js that target the shared surface pass.
|
||||
</acceptance_criteria>
|
||||
<done>DocumentDetailSurface owns the local+cloud detail layout; DocumentView is a thin provider rendering it; visible copy says Re-analyze; no Re-classify remains in these files.</done>
|
||||
</task>
|
||||
|
||||
<task type="auto" tdd="true">
|
||||
<name>Task 2: Cloud detail route + CloudDetailView.vue + getCloudItemDetail API + store force/reanalyze wiring</name>
|
||||
<read_first>
|
||||
- frontend/src/router/index.js (existing named routes /document/:id, cloud, cloud-folder; guard behavior)
|
||||
- frontend/src/api/cloud.js (openCloudFile, previewCloudFile, downloadCloudFile, enqueueAnalysis, retryAnalysisItem, jsonRequest helper — match the existing function/export style)
|
||||
- frontend/src/stores/cloudConnections.js (translateAnalysisStatus, enqueueAnalysis, retryItem, requestEstimate; reuse — do NOT add a second status translator)
|
||||
- frontend/src/views/CloudFolderView.vue (how cloud views resolve connectionId/route params, call api.* barrel, and push named routes with opaque provider_item_id)
|
||||
- frontend/src/components/storage/DocumentDetailSurface.vue (the surface this view feeds — created in Task 1)
|
||||
- .planning/phases/14.1-cloud-local-file-parity-hardening/14.1-UI-SPEC.md (Route And Navigation Contract; Detail State Contract; Preview/Download/Unsupported Contract; Re-Analyze confirmation copy)
|
||||
- .planning/phases/14.1-cloud-local-file-parity-hardening/14.1-CONTEXT.md (D-01, D-02, D-03, D-07, D-11, D-14)
|
||||
</read_first>
|
||||
<action>
|
||||
Add a named route cloud-file-detail to frontend/src/router/index.js under /cloud with an opaque provider item param, e.g. path /cloud/:connectionId/item/:itemId(.*) name 'cloud-file-detail' component () => import('../views/CloudDetailView.vue') meta { requiresAuth: true } — placed so it does not collide with the existing /cloud/:connectionId/:folderId(.*) cloud-folder route (use the distinct /item/ segment). Add getCloudItemDetail(connectionId, itemId) to frontend/src/api/cloud.js calling GET /api/cloud/connections/${connectionId}/items/${encodeURIComponent(itemId)}/detail via the same jsonRequest/get helper the other cloud methods use; never construct or expose provider URLs. Create frontend/src/views/CloudDetailView.vue as a thin data-provider view: read connectionId + itemId from route params (opaque — never split/decode provider IDs), call cloudStore/api getCloudItemDetail on mount, map the response into DocumentDetailSurface props (title=name, metadataLine from formatters, source={provider, location, isStale, statusLabel via store.translateAnalysisStatus}, topics, analysisStatus, extractedText, previewState from capabilities, downloadState from capabilities, analysisAction kind derived from analysis_status: 'analyze' when pending/none, 'reanalyze' when indexed/current/stale, 'retry' when failed/partial per D-08/Detail State Contract). Wire the surface events: preview → api.previewCloudFile (on unsupported_preview show "Preview unavailable" + backend reason and keep Download active; DO NOT auto-download — replace the CloudFolderView.onFileOpen auto-download behavior here per D-14); download → api.downloadCloudFile; analyze → store.enqueueAnalysis(scope file); reanalyze → confirm with the Copywriting Contract destructive confirmation ("Re-analyze this file? Existing extracted text and topics stay visible until the new analysis finishes."), then store.enqueueAnalysis with force:true (D-11); retry-analysis → store.retryItem or single-item retry path (D-12). Per D-07, when stale keep prior extracted_text/topics visible and promote Re-analyze. In frontend/src/stores/cloudConnections.js extend enqueueAnalysis to forward a force param into api.enqueueAnalysis params (and a convenience reanalyze action if cleaner), and add a fetchCloudItemDetail action calling api.getCloudItemDetail if components need store-level caching — keep translateAnalysisStatus as the single status source (do not add a second translator). Do not create a parallel cloud detail layout — CloudDetailView only provides data + handlers to DocumentDetailSurface.
|
||||
</action>
|
||||
<verify>
|
||||
<automated>cd frontend && npx vitest run src/views/__tests__/CloudDetailParity.test.js 2>&1 | tail -25</automated>
|
||||
</verify>
|
||||
<acceptance_criteria>
|
||||
- Named route present: `grep -c 'cloud-file-detail' frontend/src/router/index.js` returns 1 and references CloudDetailView.
|
||||
- API method present: `grep -c 'getCloudItemDetail' frontend/src/api/cloud.js` returns >= 1 and uses encodeURIComponent on itemId.
|
||||
- CloudDetailView feeds the shared surface: `grep -c 'DocumentDetailSurface' frontend/src/views/CloudDetailView.vue` returns >= 1.
|
||||
- Force re-analyze wired: `grep -nE 'force' frontend/src/views/CloudDetailView.vue frontend/src/stores/cloudConnections.js` shows force forwarded to enqueue.
|
||||
- No auto-download on unsupported preview in the detail flow: CloudDetailView's preview handler shows a reason and keeps download as a separate explicit action (confirm by reading; `grep -c 'Preview unavailable' frontend/src/views/CloudDetailView.vue` returns >= 1).
|
||||
- Only one status translator: `grep -c 'function translateAnalysisStatus' frontend/src/stores/cloudConnections.js` returns 1 and CloudDetailView contains no local status-translation function.
|
||||
- CloudDetailParity.test.js passes (route resolves, analyze→reanalyze by state, Re-analyze copy, no auto-download).
|
||||
</acceptance_criteria>
|
||||
<done>cloud-file-detail route renders CloudDetailView feeding the shared surface; getCloudItemDetail loads CloudItem detail; force re-analyze confirms then enqueues with force; unsupported preview keeps Download explicit and never auto-downloads; Plan 01 cloud-detail tests pass.</done>
|
||||
</task>
|
||||
|
||||
</tasks>
|
||||
|
||||
<threat_model>
|
||||
## Trust Boundaries
|
||||
|
||||
| Boundary | Description |
|
||||
|----------|-------------|
|
||||
| browser route → API | connectionId + opaque provider itemId from route params; must be encoded, never parsed/decoded for provider structure |
|
||||
| API response → rendered UI | rendered detail must not expose provider URLs, credentials, or cache object keys |
|
||||
|
||||
## STRIDE Threat Register
|
||||
|
||||
| Threat ID | Category | Component | Disposition | Mitigation Plan |
|
||||
|-----------|----------|-----------|-------------|-----------------|
|
||||
| T-14.1-07 | Information Disclosure | CloudDetailView render | mitigate | View renders only allowlisted CloudItemDetailOut fields; no provider URL/credential/object_key in template or store; UI-SPEC verification anchor |
|
||||
| T-14.1-08 | Tampering | provider item id in route | mitigate | itemId passed opaque, encodeURIComponent in API call, Vue Router encodes route param; never split/decode |
|
||||
| T-14.1-09 | Spoofing/Surprise download | unsupported preview | mitigate | Detail flow shows "Preview unavailable" + reason; Download is a separate explicit action (D-14); no auto-download |
|
||||
| T-14.1-SC | Tampering | npm installs | accept | No package installs (RESEARCH: none) |
|
||||
</threat_model>
|
||||
|
||||
<verification>
|
||||
- `cd frontend && npx vitest run src/views/__tests__/CloudDetailParity.test.js` passes.
|
||||
- `cd frontend && npx vitest run 2>&1 | tail -10` — full frontend suite passes (DocumentView refactor breaks nothing).
|
||||
- `grep -rc 'Re-classify' frontend/src/views frontend/src/components` returns 0 for rendered templates (test snapshots may retain intentional references — confirm none are user-facing).
|
||||
</verification>
|
||||
|
||||
<success_criteria>
|
||||
- DocumentDetailSurface is the single shared detail layout for local + cloud.
|
||||
- DocumentView and CloudDetailView are thin data providers.
|
||||
- cloud-file-detail named route + getCloudItemDetail API + force re-analyze + no-auto-download all working.
|
||||
- Visible copy says Re-analyze; single translateAnalysisStatus source preserved.
|
||||
</success_criteria>
|
||||
|
||||
<artifacts_produced>
|
||||
## Artifacts this phase produces (Plan 03)
|
||||
|
||||
- Component: `frontend/src/components/storage/DocumentDetailSurface.vue` — shared detail surface (props: title, metadataLine, source, topics, analysisStatus, extractedText, previewState, downloadState, analysisAction; emits: preview, download, reanalyze, retry-analysis, analyze).
|
||||
- View: `frontend/src/views/CloudDetailView.vue` — cloud detail data provider.
|
||||
- Route: named `cloud-file-detail` at `/cloud/:connectionId/item/:itemId(.*)` (frontend/src/router/index.js).
|
||||
- API method: `getCloudItemDetail(connectionId, itemId)` (frontend/src/api/cloud.js).
|
||||
- Store wiring: force-aware `enqueueAnalysis` (+ optional `fetchCloudItemDetail`) in frontend/src/stores/cloudConnections.js; DocumentView visible label changed to "Re-analyze".
|
||||
</artifacts_produced>
|
||||
|
||||
<output>
|
||||
Create `.planning/phases/14.1-cloud-local-file-parity-hardening/14.1-03-SUMMARY.md` when done
|
||||
</output>
|
||||
@@ -0,0 +1,176 @@
|
||||
---
|
||||
phase: 14.1-cloud-local-file-parity-hardening
|
||||
plan: 04
|
||||
type: execute
|
||||
wave: 4
|
||||
depends_on: [14.1-01, 14.1-02, 14.1-03]
|
||||
files_modified:
|
||||
- backend/api/cloud/schemas.py
|
||||
- backend/api/cloud/browse.py
|
||||
- frontend/src/components/storage/StorageBrowser.vue
|
||||
- frontend/src/views/CloudFolderView.vue
|
||||
autonomous: true
|
||||
requirements: [CLOUD-02, ANALYZE-01, ANALYZE-02, ANALYZE-03, ANALYZE-04, ANALYZE-05, ANALYZE-06, CACHE-03]
|
||||
must_haves:
|
||||
truths:
|
||||
- "Cloud browse rows carry topics, analysis_status, and current/stale state so the shared browser can render them like local rows"
|
||||
- "Local and cloud file rows show topic badges and analysis status in the same relative slot"
|
||||
- "Clicking a cloud file row navigates to the cloud detail route, not a direct preview or download"
|
||||
- "StorageBrowser uses the store translateAnalysisStatus only — the local translateStatus duplicate is removed or routed to the store"
|
||||
- "Re-analyze / Retry analysis appear in the same row action slot for local and cloud rows by state"
|
||||
artifacts:
|
||||
- path: "backend/api/cloud/browse.py"
|
||||
provides: "Browse rows include lightweight topics + analysis_status + stale state"
|
||||
contains: "analysis_status"
|
||||
- path: "frontend/src/components/storage/StorageBrowser.vue"
|
||||
provides: "Row parity: topic/status slots, state-driven analyze/reanalyze/retry, single status translator"
|
||||
contains: "translateAnalysisStatus"
|
||||
- path: "frontend/src/views/CloudFolderView.vue"
|
||||
provides: "Cloud row open navigates to cloud-file-detail route (no auto preview/download)"
|
||||
contains: "cloud-file-detail"
|
||||
key_links:
|
||||
- from: "frontend/src/views/CloudFolderView.vue"
|
||||
to: "frontend/src/router/index.js"
|
||||
via: "row open pushes named cloud-file-detail route with connectionId + opaque provider_item_id"
|
||||
pattern: "cloud-file-detail"
|
||||
- from: "frontend/src/components/storage/StorageBrowser.vue"
|
||||
to: "frontend/src/stores/cloudConnections.js"
|
||||
via: "row status rendered through store translateAnalysisStatus"
|
||||
pattern: "translateAnalysisStatus"
|
||||
- from: "frontend/src/components/storage/StorageBrowser.vue"
|
||||
to: "backend/api/cloud/browse.py"
|
||||
via: "row renders topics + analysis_status fields supplied by browse response"
|
||||
pattern: "analysis_status"
|
||||
---
|
||||
|
||||
<objective>
|
||||
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.
|
||||
|
||||
Purpose: Implements D-06, D-09, D-10, D-13, D-16 row/card parity and the UI-SPEC Browser Parity Contract. Satisfies CLOUD-02 (row-level open parity), ANALYZE-01/02/03/04/05/06 (analyze/select/folder/connection/progress/retry/idempotent affordances rendered consistently), CACHE-03 (open routes through detail/authorized handlers, no row-level byte download).
|
||||
Output: Browse response carries topics + analysis_status + stale; StorageBrowser renders parity slots and uses store translateAnalysisStatus; CloudFolderView routes row open to cloud-file-detail.
|
||||
</objective>
|
||||
|
||||
<execution_context>
|
||||
@$HOME/.claude/gsd-core/workflows/execute-plan.md
|
||||
@$HOME/.claude/gsd-core/templates/summary.md
|
||||
</execution_context>
|
||||
|
||||
<context>
|
||||
@.planning/PROJECT.md
|
||||
@.planning/ROADMAP.md
|
||||
@.planning/STATE.md
|
||||
@.planning/phases/14.1-cloud-local-file-parity-hardening/14.1-CONTEXT.md
|
||||
@.planning/phases/14.1-cloud-local-file-parity-hardening/14.1-RESEARCH.md
|
||||
@.planning/phases/14.1-cloud-local-file-parity-hardening/14.1-UI-SPEC.md
|
||||
@CLAUDE.md
|
||||
@backend/api/cloud/schemas.py
|
||||
@backend/api/cloud/browse.py
|
||||
@frontend/src/components/storage/StorageBrowser.vue
|
||||
@frontend/src/views/CloudFolderView.vue
|
||||
@frontend/src/views/FileManagerView.vue
|
||||
@frontend/src/stores/cloudConnections.js
|
||||
@frontend/src/components/storage/__tests__/StorageBrowser.parity.test.js
|
||||
</context>
|
||||
|
||||
<tasks>
|
||||
|
||||
<task type="auto" tdd="true">
|
||||
<name>Task 1: Browse rows carry lightweight topics + analysis_status + stale; StorageBrowser renders parity slots via store translator</name>
|
||||
<read_first>
|
||||
- backend/api/cloud/browse.py (_item_out builder ~line 76, browse_connection_items ~line 185 — where CloudItemOut rows are assembled)
|
||||
- backend/api/cloud/schemas.py (CloudItemOut — add lightweight row fields; keep allowlist, no extracted_text/object_key/credentials_enc)
|
||||
- backend/db/models.py (CloudItem.analysis_status, CloudItemTopic ↔ Topic for lightweight topic-name load)
|
||||
- frontend/src/components/storage/StorageBrowser.vue (file row name cell ~line 575-660, topics in name cell ~line 49, analyze-file slot ~line 614, translateStatus def ~line 1022, analysisQueue usage ~line 304)
|
||||
- frontend/src/stores/cloudConnections.js (translateAnalysisStatus — the single translator)
|
||||
- frontend/src/views/FileManagerView.vue (local row: how file.topics and status render — the parity reference)
|
||||
- .planning/phases/14.1-cloud-local-file-parity-hardening/14.1-UI-SPEC.md (Browser Parity Contract: title area, analysis affordance slot, status badges zone, disabled actions)
|
||||
- frontend/src/components/storage/__tests__/StorageBrowser.parity.test.js (exact row parity assertions)
|
||||
</read_first>
|
||||
<action>
|
||||
Backend: extend CloudItemOut in backend/api/cloud/schemas.py with lightweight, allowlisted row fields only — topics as a list of topic-name strings defaulting to empty, analysis_status as an optional string, and is_stale as a bool defaulting to false. DO NOT add extracted_text to row output (avoid list-payload inflation per RESEARCH common pitfall; full text stays on the detail endpoint). Keep CloudItemOut free of the credential field, the MinIO object-key field, the version-key field, and any provider URL. In backend/api/cloud/browse.py update _item_out (and the row assembly in browse_connection_items) to populate analysis_status from CloudItem.analysis_status, is_stale from a comparison of analysis_status to the stale literal, and topics from a lightweight CloudItemTopic→Topic name load. Batch the topic-name lookup for the page's file ids in one query to avoid N+1. Folder rows leave topics empty and analysis_status null. Frontend: in frontend/src/components/storage/StorageBrowser.vue render the cloud row's analysis status in the SAME relative slot as the local row status (name-cell zone), reuse the existing topic-badge block so cloud rows now display file.topics received from browse, and ensure the analyze/re-analyze/retry affordance occupies one shared name-cell slot that swaps by state (analyze when not analyzed, re-analyze when indexed/stale, retry when failed) per the UI-SPEC Browser Parity + Re-Analyze contracts. Replace the local translateStatus helper usage so status text comes from the store's translateAnalysisStatus (use the cloudConnections store translator) — remove the duplicate local function or make it delegate to the store, satisfying CLAUDE.md's single-translation-source rule (D-06). Use status colors per UI-SPEC: green current, amber stale/skipped, blue or violet working, red failed. Do not introduce a second cloud-only toolbar for row affordances — they stay inline in the name cell.
|
||||
</action>
|
||||
<verify>
|
||||
<automated>cd backend && python -m pytest tests/test_cloud.py tests/test_cloud_detail_parity.py -x 2>&1 | tail -12 && cd ../frontend && npx vitest run src/components/storage/__tests__/StorageBrowser.parity.test.js src/components/storage/__tests__/StorageBrowser.cloud-queue.test.js src/components/storage/__tests__/StorageBrowser.capabilities.test.js 2>&1 | tail -20</automated>
|
||||
</verify>
|
||||
<acceptance_criteria>
|
||||
- CloudItemOut gains lightweight fields: `grep -nE 'analysis_status|is_stale|topics' backend/api/cloud/schemas.py` shows them inside the CloudItemOut class; `grep -c 'extracted_text' backend/api/cloud/schemas.py` does NOT include CloudItemOut (extracted_text only on detail schema).
|
||||
- browse.py populates them: `grep -c 'analysis_status' backend/api/cloud/browse.py` returns >= 1.
|
||||
- No N+1: the topic-name load for browse rows is a single batched query (confirm by reading; one select over the page's cloud_item ids).
|
||||
- StorageBrowser uses store translator: `grep -c 'translateAnalysisStatus' frontend/src/components/storage/StorageBrowser.vue` returns >= 1 and the local duplicate either delegates or is removed (`grep -c 'function translateStatus' frontend/src/components/storage/StorageBrowser.vue` returns 0, or the remaining function body calls the store translator).
|
||||
- StorageBrowser.parity.test.js paired row assertions pass (topic badges + status slot present in both local and cloud rows).
|
||||
- Existing StorageBrowser.cloud-queue.test.js and StorageBrowser.capabilities.test.js still pass (no regression).
|
||||
</acceptance_criteria>
|
||||
<done>Cloud rows render topics + analysis status in the same slots as local rows using the store translator; browse response carries lightweight topics/analysis_status/is_stale without extracted_text; existing browser tests pass.</done>
|
||||
</task>
|
||||
|
||||
<task type="auto" tdd="true">
|
||||
<name>Task 2: Cloud row open navigates to cloud-file-detail route (no auto preview/download)</name>
|
||||
<read_first>
|
||||
- frontend/src/views/CloudFolderView.vue (onFileOpen ~line 420 — currently calls openCloudFile and auto-falls-back to downloadCloudFile on unsupported_preview; router.push named-route patterns ~line 140-160)
|
||||
- frontend/src/router/index.js (cloud-file-detail named route added in Plan 03)
|
||||
- frontend/src/views/FileManagerView.vue (local file-open → /document/:id — the parity reference)
|
||||
- .planning/phases/14.1-cloud-local-file-parity-hardening/14.1-UI-SPEC.md (Route And Navigation Contract: cloud row opens detail first, no auto-preview/auto-download)
|
||||
- .planning/phases/14.1-cloud-local-file-parity-hardening/14.1-CONTEXT.md (D-01, D-14)
|
||||
- frontend/src/views/__tests__/CloudFolderView.test.js and CloudFolderOpenPreview.test.js (existing open behavior to update — note Phase 13 P04 D-02 forbids window.open to provider URL; this stays a DocuVault route push)
|
||||
</read_first>
|
||||
<action>
|
||||
Change onFileOpen in frontend/src/views/CloudFolderView.vue so a cloud file row click navigates to the cloud detail route instead of calling openCloudFile + auto-download. Replace the body with router.push({ name: 'cloud-file-detail', params: { connectionId: connectionId.value, itemId: file.provider_item_id } }) — passing the opaque provider_item_id as the route param (Vue Router encodes it; never split or decode it). Preview and download become explicit actions on the detail surface (implemented in Plan 03), NOT side effects of a row click; remove the auto-download-on-unsupported_preview branch from the row open path (D-14). Keep folder navigation (folder-navigate) unchanged. If any existing CloudFolderView open/preview test asserts the old auto-open/auto-download behavior, update that test to assert the new navigation-to-detail behavior (the row click pushes cloud-file-detail; it does NOT call downloadCloudFile). Do not introduce window.open or any provider URL navigation — the row click only pushes the DocuVault-internal named route (preserves Phase 13 D-02). Confirm local FileManagerView row open behavior to /document/:id is untouched so the parity test holds.
|
||||
</action>
|
||||
<verify>
|
||||
<automated>cd frontend && npx vitest run src/views/__tests__/CloudFolderView.test.js src/views/__tests__/CloudFolderOpenPreview.test.js src/views/__tests__/CloudDetailParity.test.js 2>&1 | tail -25</automated>
|
||||
</verify>
|
||||
<acceptance_criteria>
|
||||
- Row open pushes the named detail route: `grep -c 'cloud-file-detail' frontend/src/views/CloudFolderView.vue` returns >= 1.
|
||||
- The auto-download branch is gone from the row open path: `grep -n 'downloadCloudFile' frontend/src/views/CloudFolderView.vue` shows no call inside onFileOpen (download may still exist for explicit handlers elsewhere, but not on row click).
|
||||
- No provider-URL navigation: `grep -c 'window.open' frontend/src/views/CloudFolderView.vue` returns 0.
|
||||
- The row-click navigation test in CloudDetailParity.test.js passes (cloud row → cloud-file-detail; local row → /document/:id).
|
||||
- Updated CloudFolderView.test.js / CloudFolderOpenPreview.test.js pass with the new navigate-to-detail behavior.
|
||||
</acceptance_criteria>
|
||||
<done>Cloud file row clicks navigate to the cloud-file-detail route with the opaque provider_item_id; no auto preview/download on row click; no provider-URL navigation; local row behavior unchanged; affected tests pass.</done>
|
||||
</task>
|
||||
|
||||
</tasks>
|
||||
|
||||
<threat_model>
|
||||
## Trust Boundaries
|
||||
|
||||
| Boundary | Description |
|
||||
|----------|-------------|
|
||||
| browse response → row render | rows carry only allowlisted lightweight fields (topics names, analysis_status) — no extracted_text/object_key/credentials/provider URL |
|
||||
| row click → navigation | navigates to a DocuVault-internal named route with an opaque provider id; never to a provider URL |
|
||||
|
||||
## STRIDE Threat Register
|
||||
|
||||
| Threat ID | Category | Component | Disposition | Mitigation Plan |
|
||||
|-----------|----------|-----------|-------------|-----------------|
|
||||
| T-14.1-10 | Information Disclosure | CloudItemOut row fields | mitigate | Row schema stays an allowlist; extracted_text excluded from rows; no object_key/credentials/provider URL; Plan 01/04 tests enforce |
|
||||
| T-14.1-11 | Spoofing/Surprise navigation | cloud row click | mitigate | Row click pushes internal cloud-file-detail route only; no window.open / provider URL (preserves Phase 13 D-02) |
|
||||
| T-14.1-12 | Surprise download | unsupported preview on row | mitigate | Auto-download removed from row open path; download is explicit on detail (D-14) |
|
||||
| T-14.1-SC | Tampering | npm/pip installs | accept | No package installs (RESEARCH: none) |
|
||||
</threat_model>
|
||||
|
||||
<verification>
|
||||
- `cd backend && python -m pytest tests/test_cloud.py tests/test_cloud_detail_parity.py tests/test_cloud_security.py -x` passes.
|
||||
- `cd frontend && npx vitest run src/components/storage/__tests__ src/views/__tests__/CloudFolderView.test.js src/views/__tests__/CloudFolderOpenPreview.test.js src/views/__tests__/CloudDetailParity.test.js` passes.
|
||||
- `cd frontend && npx vitest run 2>&1 | tail -10` — full frontend suite passes.
|
||||
</verification>
|
||||
|
||||
<success_criteria>
|
||||
- Browse rows carry lightweight topics + analysis_status + is_stale (no extracted_text in rows).
|
||||
- Local and cloud rows render topic badges + analysis status in the same relative slots via the store translator.
|
||||
- Cloud row click navigates to cloud-file-detail; no auto preview/download; no provider URL.
|
||||
- Existing browser/cloud-view tests pass with updated navigation behavior.
|
||||
</success_criteria>
|
||||
|
||||
<artifacts_produced>
|
||||
## Artifacts this phase produces (Plan 04)
|
||||
|
||||
- Fields: `topics: list[str]`, `analysis_status: Optional[str]`, `is_stale: bool` added to `CloudItemOut` (backend/api/cloud/schemas.py).
|
||||
- Behavior: browse rows populate topics (batched) + analysis_status + is_stale (backend/api/cloud/browse.py).
|
||||
- Behavior: StorageBrowser renders cloud row topic/status parity slots and uses store `translateAnalysisStatus` (local `translateStatus` duplicate removed/delegated) (frontend/src/components/storage/StorageBrowser.vue).
|
||||
- Behavior: `onFileOpen` in CloudFolderView pushes `cloud-file-detail` named route with opaque provider_item_id; auto preview/download removed from row open (frontend/src/views/CloudFolderView.vue).
|
||||
</artifacts_produced>
|
||||
|
||||
<output>
|
||||
Create `.planning/phases/14.1-cloud-local-file-parity-hardening/14.1-04-SUMMARY.md` when done
|
||||
</output>
|
||||
@@ -0,0 +1,183 @@
|
||||
---
|
||||
phase: 14.1-cloud-local-file-parity-hardening
|
||||
plan: 05
|
||||
type: execute
|
||||
wave: 5
|
||||
depends_on: [14.1-01, 14.1-02, 14.1-03, 14.1-04]
|
||||
files_modified:
|
||||
- backend/main.py
|
||||
- frontend/package.json
|
||||
- CLAUDE.md
|
||||
- README.md
|
||||
autonomous: false
|
||||
requirements: [CLOUD-02, ANALYZE-01, ANALYZE-02, ANALYZE-03, ANALYZE-04, ANALYZE-05, ANALYZE-06, ANALYZE-07, CACHE-03, CACHE-04, CACHE-05]
|
||||
must_haves:
|
||||
truths:
|
||||
- "Full backend and frontend test suites pass with zero failures"
|
||||
- "Security gate passes: owner/admin negatives, no credential/object_key/provider URL leakage, dependency audits clean"
|
||||
- "CLAUDE.md and README reflect the cloud detail parity surface, route, and force re-analyze"
|
||||
- "App version is bumped per protocol and committed atomically"
|
||||
artifacts:
|
||||
- path: "CLAUDE.md"
|
||||
provides: "Updated current-state line, shared module map (DocumentDetailSurface, cloud detail route/endpoint), non-negotiable parity rules"
|
||||
contains: "DocumentDetailSurface"
|
||||
- path: "backend/main.py"
|
||||
provides: "Bumped version"
|
||||
contains: "version="
|
||||
- path: "frontend/package.json"
|
||||
provides: "Bumped version matching backend"
|
||||
contains: "version"
|
||||
key_links:
|
||||
- from: "CLAUDE.md"
|
||||
to: "frontend/src/components/storage/DocumentDetailSurface.vue"
|
||||
via: "shared module map documents the shared detail surface"
|
||||
pattern: "DocumentDetailSurface"
|
||||
- from: "backend/main.py"
|
||||
to: "frontend/package.json"
|
||||
via: "version strings match"
|
||||
pattern: "version"
|
||||
---
|
||||
|
||||
<objective>
|
||||
Close out Phase 14.1: run the full backend + frontend test suites, run the mandatory security gate (owner/admin negatives, credential/object_key/provider-URL leakage scan, dependency + secret audits), update CLAUDE.md and README for the cloud detail parity surface, bump the app version per protocol, and commit/push atomically.
|
||||
|
||||
Purpose: Satisfies the Mandatory Cross-Cutting Gates (tests pass, security gate, dependency audits, docs/version updates, atomic commit) and the CLAUDE.md Documentation/Testing/Security/Git protocols before Phase 14.1 is marked complete. This is the only plan that touches docs and versions (no implementation logic).
|
||||
Output: Green full suites, passed security gate, updated CLAUDE.md/README, bumped version, one atomic commit pushed.
|
||||
</objective>
|
||||
|
||||
<execution_context>
|
||||
@$HOME/.claude/gsd-core/workflows/execute-plan.md
|
||||
@$HOME/.claude/gsd-core/templates/summary.md
|
||||
</execution_context>
|
||||
|
||||
<context>
|
||||
@.planning/PROJECT.md
|
||||
@.planning/ROADMAP.md
|
||||
@.planning/STATE.md
|
||||
@.planning/phases/14.1-cloud-local-file-parity-hardening/14.1-CONTEXT.md
|
||||
@.planning/phases/14.1-cloud-local-file-parity-hardening/14.1-RESEARCH.md
|
||||
@CLAUDE.md
|
||||
@README.md
|
||||
@backend/main.py
|
||||
@frontend/package.json
|
||||
</context>
|
||||
|
||||
<tasks>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 1: Full test suites + security gate + dependency/secret audits</name>
|
||||
<read_first>
|
||||
- CLAUDE.md (Testing Protocol, Security Protocol, Security gate checklist — the exact gate commands)
|
||||
- backend/tests/test_cloud_security.py (owner/admin/no-leak coverage that must include the new detail endpoint and force re-analyze)
|
||||
- backend/tests/test_cloud_detail_parity.py and backend/tests/test_cloud_reanalyze_force.py (Plan 01 suites — must be GREEN now)
|
||||
- frontend/src/views/__tests__/CloudDetailParity.test.js and frontend/src/components/storage/__tests__/StorageBrowser.parity.test.js (Plan 01 frontend suites — must be GREEN now)
|
||||
- .planning/phases/14.1-cloud-local-file-parity-hardening/14.1-RESEARCH.md (Security Domain ASVS V4/V8/V12 targets; Package Legitimacy Audit: no new packages)
|
||||
</read_first>
|
||||
<action>
|
||||
Run the full backend suite (cd backend && python -m pytest -v) and the full frontend suite (cd frontend && npm run test or npx vitest run) — both MUST pass with zero failures; fix any regressions introduced by Plans 02-04 at root cause (≤50 lines per fix per CLAUDE.md; larger means a separate plan). Run the security gate per the CLAUDE.md Security gate checklist: bandit -r backend/ (zero HIGH), pip audit (zero critical/high), npm audit --audit-level=high (zero high/critical). Verify the cloud detail endpoint and force re-analyze path are covered by owner/admin-negative and no-leak assertions — if test_cloud_security.py does not yet exercise the new detail route and force enqueue, add focused negative tests there (foreign user 404, admin 403, response excludes credentials/object_key/provider URL). Run an executable secret scan over the diff (the repo's existing secret-scan approach, e.g. trufflehog/git secrets if configured) and confirm no credentials, tokens, provider URLs, or object keys are present in code, tests, or planning artifacts. Confirm no provider bytes are downloaded during browse/detail/estimate (the metadata-only invariant) by confirming the relevant cache/analysis contract tests pass. Do NOT install new packages (RESEARCH: none required) — if any audit flags an existing CVE, fix by version bump in a separate chore commit before this closeout commit.
|
||||
</action>
|
||||
<verify>
|
||||
<automated>cd backend && python -m pytest -q 2>&1 | tail -8 && bandit -r backend/ -lll 2>&1 | tail -5 && cd ../frontend && npx vitest run 2>&1 | tail -8 && npm audit --audit-level=high 2>&1 | tail -5</automated>
|
||||
</verify>
|
||||
<acceptance_criteria>
|
||||
- Backend full suite: `cd backend && python -m pytest -q` reports 0 failed.
|
||||
- Frontend full suite: `cd frontend && npx vitest run` reports 0 failed.
|
||||
- bandit -r backend/ reports 0 HIGH severity findings.
|
||||
- npm audit --audit-level=high reports 0 high/critical vulnerabilities.
|
||||
- test_cloud_security.py (or test_cloud_detail_parity.py) includes the detail-endpoint and force-enqueue owner/admin/no-leak negatives: `grep -nE 'detail|force' backend/tests/test_cloud_security.py backend/tests/test_cloud_detail_parity.py` shows coverage.
|
||||
- No secrets/object keys/provider URLs in the diff (secret scan clean).
|
||||
</acceptance_criteria>
|
||||
<done>Full backend + frontend suites pass; bandit/pip/npm audits clean; detail + force re-analyze covered by owner/admin/no-leak negatives; secret scan clean.</done>
|
||||
</task>
|
||||
|
||||
<task type="checkpoint:human-verify" gate="blocking">
|
||||
<name>Checkpoint: Human verification of cloud/local file parity</name>
|
||||
<action>Human verifies the cloud detail view, browser row parity, force re-analyze confirmation, and unsupported-preview-keeps-download behavior against a running app. See what-built and how-to-verify below.</action>
|
||||
<what-built>
|
||||
Cloud/local file parity: a cloud detail view (route cloud-file-detail) backed by CloudItem, shared DocumentDetailSurface used by local + cloud detail, browser row parity (topics + analysis status + Re-analyze/Retry slots), force re-analyze with confirmation, and unsupported-preview-keeps-download-explicit behavior.
|
||||
</what-built>
|
||||
<how-to-verify>
|
||||
1. Start the app: `docker compose up` (or backend `uvicorn main:app --reload` + frontend `npm run dev`).
|
||||
2. Log in, open /cloud, navigate into a connected provider folder.
|
||||
3. Click a cloud file row — confirm it opens a DETAIL view (URL contains /cloud/.../item/...), NOT an immediate preview or download.
|
||||
4. On an un-analyzed cloud file: confirm the detail shows "No analysis yet" + an "Analyze file" action; trigger Analyze and watch status progress.
|
||||
5. On an analyzed cloud file: confirm extracted text, topic badges, analysis status, and a "Re-analyze" action appear (NO "Re-classify" text anywhere).
|
||||
6. Click Re-analyze on a current file: confirm a confirmation dialog appears ("Re-analyze this file? Existing extracted text and topics stay visible…") and proceeding re-runs analysis without losing prior text/topics.
|
||||
7. On a file whose preview is unsupported: confirm "Preview unavailable" + reason shows and Download stays active and does NOT auto-download.
|
||||
8. Open a LOCAL document at /document/:id and confirm the detail layout (header, topics, extracted text, Re-analyze) matches the cloud detail layout.
|
||||
9. In the browser grid, confirm cloud rows show topic badges + analysis status in the same place as local rows.
|
||||
</how-to-verify>
|
||||
<resume-signal>Type "approved" or describe any parity/behavior issues to fix</resume-signal>
|
||||
</task>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 2: Update CLAUDE.md + README, bump version, atomic commit + push</name>
|
||||
<read_first>
|
||||
- CLAUDE.md (Current state line in GSD Workflow section; shared module map tables backend + frontend; Documentation Protocol; Git Protocol; version bump rule)
|
||||
- README.md (Features section, env var table, version reference)
|
||||
- backend/main.py (FastAPI version="0.4.0")
|
||||
- frontend/package.json ("version": "0.4.0")
|
||||
- .planning/ROADMAP.md (Phase 14.1 plan checklist to tick)
|
||||
</read_first>
|
||||
<action>
|
||||
Update CLAUDE.md: change the "Current state" line in the GSD Workflow section to record Phase 14.1 complete (cloud/local file parity: cloud detail route + view, shared DocumentDetailSurface, browser row parity, force re-analyze, unsupported-preview keeps download explicit). Add to the FRONTEND shared module map a row for frontend/src/components/storage/DocumentDetailSurface.vue (shared detail surface used by DocumentView and CloudDetailView) and note CloudDetailView.vue + the cloud-file-detail route as thin data providers. Add to the BACKEND shared module map the CloudItemDetailOut schema and resolve_owned_cloud_item_detail (owner-scoped cloud detail + topics, metadata-only), the GET .../items/{id}/detail route, and the force flag on enqueue. Add non-negotiable rules: visible copy says "Re-analyze" not "Re-classify"; cloud row click opens the cloud detail route (no auto preview/download); cloud detail resolution is metadata-only (no byte hydration); CloudItemDetailOut/CloudItemOut remain allowlists excluding object_key/credentials_enc/provider URL. Update README.md if any user-facing feature/route/behavior changed (add cloud file detail view + Re-analyze to the Features section; no new env vars expected). Bump the version: per CLAUDE.md protocol, Phase 14.1 is an inserted hardening phase shipping user-facing changes — bump the PATCH segment in backend/main.py (version="0.4.0" → "0.4.1") and frontend/package.json ("0.4.0" → "0.4.1") so both match. Tick the Phase 14.1 plan checkboxes in ROADMAP.md. Stage explicitly and commit atomically per CLAUDE.md Git Protocol: `git add backend/ frontend/ CLAUDE.md README.md .planning/` then commit `feat(14.1): cloud/local file parity — shared detail surface, cloud detail route, force re-analyze`, then push. Do NOT use git add -A.
|
||||
</action>
|
||||
<verify>
|
||||
<automated>cd backend && grep -c 'version="0.4.1"' main.py && cd ../frontend && grep -c '"version": "0.4.1"' package.json && cd .. && grep -c 'DocumentDetailSurface' CLAUDE.md && grep -c 'Phase 14.1' CLAUDE.md</automated>
|
||||
</verify>
|
||||
<acceptance_criteria>
|
||||
- backend/main.py version is "0.4.1": `grep -c 'version="0.4.1"' backend/main.py` returns 1.
|
||||
- frontend/package.json version is "0.4.1": `grep -c '"version": "0.4.1"' frontend/package.json` returns 1.
|
||||
- CLAUDE.md documents the shared surface and phase completion: `grep -c 'DocumentDetailSurface' CLAUDE.md` >= 1 and the Current state line references Phase 14.1.
|
||||
- CLAUDE.md adds the cloud detail backend symbols: `grep -cE 'CloudItemDetailOut|resolve_owned_cloud_item_detail' CLAUDE.md` >= 1.
|
||||
- ROADMAP Phase 14.1 plans are checked off and a single atomic commit exists: `git log --oneline -1` shows the feat(14.1) message.
|
||||
- The commit is pushed (git status shows clean / ahead-by-0 after push).
|
||||
</acceptance_criteria>
|
||||
<done>CLAUDE.md + README updated, both versions bumped to 0.4.1, ROADMAP ticked, one atomic feat(14.1) commit created and pushed.</done>
|
||||
</task>
|
||||
|
||||
</tasks>
|
||||
|
||||
<threat_model>
|
||||
## Trust Boundaries
|
||||
|
||||
| Boundary | Description |
|
||||
|----------|-------------|
|
||||
| diff → repo | committed code/tests/docs must contain no secrets, tokens, provider URLs, or object keys |
|
||||
| dependency tree → app | audits must show no high/critical CVEs |
|
||||
|
||||
## STRIDE Threat Register
|
||||
|
||||
| Threat ID | Category | Component | Disposition | Mitigation Plan |
|
||||
|-----------|----------|-----------|-------------|-----------------|
|
||||
| T-14.1-13 | Information Disclosure | committed artifacts | mitigate | Secret scan over diff; no credentials/object_key/provider URL in code/tests/planning |
|
||||
| T-14.1-14 | Tampering / Vulnerable deps | dependency tree | mitigate | bandit + pip audit + npm audit gate; no new packages added |
|
||||
| T-14.1-15 | Elevation of Privilege | detail + force endpoints (regression) | mitigate | Security gate re-runs owner/admin negatives on the new surfaces |
|
||||
| T-14.1-SC | Tampering | npm/pip installs | accept | No package installs this phase (RESEARCH Package Legitimacy Audit: none) |
|
||||
</threat_model>
|
||||
|
||||
<verification>
|
||||
- `cd backend && python -m pytest -q` → 0 failed; `cd frontend && npx vitest run` → 0 failed.
|
||||
- `bandit -r backend/ -lll`, `pip audit`, `npm audit --audit-level=high` → clean.
|
||||
- Versions match at 0.4.1; CLAUDE.md + README updated; ROADMAP ticked; atomic commit pushed.
|
||||
- Human checkpoint approved for visual/interaction parity.
|
||||
</verification>
|
||||
|
||||
<success_criteria>
|
||||
- All gates green (tests, security, audits, secret scan).
|
||||
- Docs + versions updated and committed atomically; pushed.
|
||||
- Human verification of cloud/local parity approved.
|
||||
</success_criteria>
|
||||
|
||||
<artifacts_produced>
|
||||
## Artifacts this phase produces (Plan 05)
|
||||
|
||||
- Version bump: backend/main.py and frontend/package.json → 0.4.1.
|
||||
- Docs: CLAUDE.md updated (current-state line, shared module maps for DocumentDetailSurface + CloudItemDetailOut/resolve_owned_cloud_item_detail + cloud detail route, new non-negotiable parity rules); README Features section updated.
|
||||
- ROADMAP: Phase 14.1 plan checkboxes ticked.
|
||||
- One atomic `feat(14.1)` commit, pushed.
|
||||
</artifacts_produced>
|
||||
|
||||
<output>
|
||||
Create `.planning/phases/14.1-cloud-local-file-parity-hardening/14.1-05-SUMMARY.md` when done
|
||||
</output>
|
||||
@@ -1,10 +1,11 @@
|
||||
---
|
||||
phase: 14.1
|
||||
slug: cloud-local-file-parity-hardening
|
||||
status: draft
|
||||
status: approved
|
||||
shadcn_initialized: false
|
||||
preset: none
|
||||
created: 2026-06-26
|
||||
reviewed_at: 2026-06-26T18:51:13Z
|
||||
---
|
||||
|
||||
# Phase 14.1 — UI Design Contract
|
||||
@@ -39,7 +40,7 @@ Declared values (must be multiples of 4):
|
||||
| 2xl | 48px | Major section separation |
|
||||
| 3xl | 64px | Reserved for full-page spacing only |
|
||||
|
||||
Exceptions: `36px` minimum dense icon-button targets inside `StorageBrowser.vue` rows; `44px` minimum for modal and confirmation actions; sticky browser headers keep `12px` vertical padding with `16px` mobile and `24px` desktop horizontal padding.
|
||||
Dense row icon buttons use `32px` targets inside `StorageBrowser.vue` rows. Sticky browser headers use `16px` vertical padding with `16px` mobile and `24px` desktop horizontal padding. Modal, footer, confirmation, and primary actions use `48px` minimum targets.
|
||||
|
||||
---
|
||||
|
||||
@@ -73,9 +74,9 @@ Warning and stale states use the existing amber family (`amber-50/200/500/800`),
|
||||
|
||||
| Element | Copy |
|
||||
|---------|------|
|
||||
| Primary CTA | `Analyze file` when no analysis exists; otherwise the first content action is `Preview` or `Open` and analysis becomes `Re-analyze` |
|
||||
| Primary CTA | `Analyze file` when no analysis exists; otherwise the first content action is `Preview file` or `Open file` and analysis becomes `Re-analyze` |
|
||||
| Empty state heading | `No analysis yet` |
|
||||
| Empty state body | `Analyze this file to extract text and topics.` Follow with a subtle source note for cloud files: `Preview, download, and analysis still run through DocuVault.` |
|
||||
| Empty state body | `Analyze this file to extract text and topics.` Follow with a subtle source note for cloud files: `Preview file, download, and analysis still run through DocuVault.` |
|
||||
| Error state | `This file could not be fully analyzed.` Follow with the next step: `Retry analysis or re-analyze to refresh the result.` |
|
||||
| Destructive confirmation | `Re-analyze this file? Existing extracted text and topics stay visible until the new analysis finishes.` |
|
||||
|
||||
@@ -109,7 +110,7 @@ Visible copy rule: user-facing text must say `Re-analyze` everywhere. `Re-classi
|
||||
2. Cloud rows open a dedicated detail route, named `cloud-file-detail` or an equivalent explicit named route under `/cloud/:connectionId/...`.
|
||||
3. Cloud route params use `connectionId` plus opaque provider item identity. Vue Router handles encoding; frontend code must not split or infer structure from provider IDs.
|
||||
4. Clicking a cloud row opens detail first. It does not auto-preview and does not auto-download.
|
||||
5. Preview/Open and Download are explicit actions from the detail header and any mirrored row action slots.
|
||||
5. `Preview file` / `Open file` and Download are explicit actions from the detail header and any mirrored row action slots.
|
||||
|
||||
---
|
||||
|
||||
@@ -139,6 +140,8 @@ The shared detail surface uses this section order for both local and cloud files
|
||||
5. Extracted text section
|
||||
6. Secondary controls and inline error/help copy
|
||||
|
||||
The filename plus primary action cluster is the first visual anchor on detail screens.
|
||||
|
||||
Header rules:
|
||||
|
||||
- Display title uses `24px` semibold with wrapping allowed for long filenames.
|
||||
@@ -149,7 +152,7 @@ Header rules:
|
||||
Card rules:
|
||||
|
||||
- Detail sections stay white with `border-gray-200` and `rounded-xl`.
|
||||
- Internal section padding is `20-24px`.
|
||||
- Internal section padding is `24px`.
|
||||
- Section headings use `18px` semibold for top-level detail sections and `14px` semibold for compact labels.
|
||||
|
||||
---
|
||||
@@ -169,10 +172,10 @@ Card rules:
|
||||
|
||||
## Preview, Download, And Unsupported-State Contract
|
||||
|
||||
1. Preview and Download stay adjacent in the detail header action cluster.
|
||||
2. If preview is supported, `Preview` or `Open` is the first content action.
|
||||
3. If preview is unsupported but download is supported, Preview remains visible but disabled, with helper copy `Preview unavailable` plus the backend reason. Download remains active.
|
||||
4. Preview never triggers an automatic download.
|
||||
1. `Preview file` and Download stay adjacent in the detail header action cluster; when inline opening is the correct capability, the label becomes `Open file`.
|
||||
2. If preview is supported, `Preview file` or `Open file` is the first content action.
|
||||
3. If preview is unsupported but download is supported, `Preview file` remains visible but disabled, with helper copy `Preview unavailable` plus the backend reason. Download remains active.
|
||||
4. `Preview file` never triggers an automatic download.
|
||||
5. No cloud UI may expose provider URLs, provider hostnames, raw tokens, or cache object keys.
|
||||
6. Any content action copy that differs between local and cloud must be justified by ownership or capability, not by layout convenience.
|
||||
|
||||
@@ -190,11 +193,12 @@ Card rules:
|
||||
|
||||
## Accessibility And Feedback Contract
|
||||
|
||||
1. Dense row icon buttons may use `36px` targets; modal, footer, and primary actions use `44px` minimum targets.
|
||||
2. Disabled actions must remain keyboard-focusable only when they can surface an explanatory reason; otherwise they are non-interactive and accompanied by inline explanatory text.
|
||||
3. Status banners use semantic color plus icon plus text; color alone is insufficient.
|
||||
4. Focus indicators use the indigo ring pattern already present in the app.
|
||||
5. Long filenames, provider locations, and error reasons must wrap instead of truncating inside the detail header and status areas.
|
||||
1. Dense row icon buttons use `32px` targets; modal, footer, and primary actions use `48px` minimum targets.
|
||||
2. Dense icon-only row actions require a tooltip and a visible text fallback anywhere hover is unavailable or the action collapses into a menu.
|
||||
3. Disabled actions must remain keyboard-focusable only when they can surface an explanatory reason; otherwise they are non-interactive and accompanied by inline explanatory text.
|
||||
4. Status banners use semantic color plus icon plus text; color alone is insufficient.
|
||||
5. Focus indicators use the indigo ring pattern already present in the app.
|
||||
6. Long filenames, provider locations, and error reasons must wrap instead of truncating inside the detail header and status areas.
|
||||
|
||||
---
|
||||
|
||||
@@ -205,7 +209,7 @@ The implementation must be testable against these UI outcomes:
|
||||
- Paired local/cloud browser assertions verify identical row title structure, topic placement, status placement, and analysis-action slots.
|
||||
- Paired local/cloud detail assertions verify identical section order, action order, and stale/partial/failed rendering rules.
|
||||
- Cloud row click opens detail instead of immediate preview/download.
|
||||
- Unsupported preview keeps Download active and never auto-downloads from the Preview trigger.
|
||||
- Unsupported preview keeps Download active and never auto-downloads from the `Preview file` trigger.
|
||||
- No rendered output or API-fed UI state leaks provider URLs, credentials, or cache object keys.
|
||||
- Visible UI contains `Re-analyze` and no surviving `Re-classify` copy.
|
||||
|
||||
@@ -213,11 +217,11 @@ The implementation must be testable against these UI outcomes:
|
||||
|
||||
## Checker Sign-Off
|
||||
|
||||
- [ ] Dimension 1 Copywriting: PASS
|
||||
- [ ] Dimension 2 Visuals: PASS
|
||||
- [ ] Dimension 3 Color: PASS
|
||||
- [ ] Dimension 4 Typography: PASS
|
||||
- [ ] Dimension 5 Spacing: PASS
|
||||
- [ ] Dimension 6 Registry Safety: PASS
|
||||
- [x] Dimension 1 Copywriting: PASS
|
||||
- [x] Dimension 2 Visuals: PASS
|
||||
- [x] Dimension 3 Color: PASS
|
||||
- [x] Dimension 4 Typography: PASS
|
||||
- [x] Dimension 5 Spacing: PASS
|
||||
- [x] Dimension 6 Registry Safety: PASS
|
||||
|
||||
**Approval:** pending
|
||||
**Approval:** approved
|
||||
|
||||
Reference in New Issue
Block a user