docs(phase-6.2): record planning complete — 4 plans verified, state updated

- ROADMAP.md: progress table → Planned; wave annotations already added by planner
- STATE.md: phase 6.2 row → Planned (4 plans, 3 waves); session note added
- 06.2-03-PLAN.md: remove incorrect SHARE-03/SHARE-05 from requirements field
- 06.2-RESEARCH.md: mark Open Questions section as RESOLVED
- 06.2-UI-SPEC.md: add to version control (was untracked)

Verification: 0 blockers, 2 cosmetic warnings fixed.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
curo1305
2026-05-31 11:41:32 +02:00
parent 4adc77d8cc
commit 708fd7fad0
5 changed files with 326 additions and 8 deletions
+1 -1
View File
@@ -369,4 +369,4 @@ Before any phase is marked complete, all three gates must pass:
| 5. Cloud Storage Backends | 12/12 | Complete | 2026-05-30 | | 5. Cloud Storage Backends | 12/12 | Complete | 2026-05-30 |
| 6. Performance & Production Hardening | 0/TBD | Not started | — | | 6. Performance & Production Hardening | 0/TBD | Not started | — |
| 6.1. Close v1.0 audit gaps | 2/2 | Complete | 2026-05-30 | | 6.1. Close v1.0 audit gaps | 2/2 | Complete | 2026-05-30 |
| 6.2. Close v1 sharing + cloud-delete + CSV export gaps | 0/4 | Not started | — | | 6.2. Close v1 sharing + cloud-delete + CSV export gaps | 0/4 | Planned | — |
+3 -2
View File
@@ -31,7 +31,7 @@ progress:
| 5 | Cloud Storage Backends | ✓ Complete (12/12 plans, UAT 5/6 passed, 3 gaps closed by 05-12) | | 5 | Cloud Storage Backends | ✓ Complete (12/12 plans, UAT 5/6 passed, 3 gaps closed by 05-12) |
| 6 | Performance & Production Hardening | Not started | | 6 | Performance & Production Hardening | Not started |
| 6.1 | Close v1.0 audit gaps: SHARE-02/STORE-06/ADMIN-06 | ✓ Complete (2/2 plans) | | 6.1 | Close v1.0 audit gaps: SHARE-02/STORE-06/ADMIN-06 | ✓ Complete (2/2 plans) |
| 6.2 | Close v1 sharing + cloud-delete + CSV export gaps | Not started | | 6.2 | Close v1 sharing + cloud-delete + CSV export gaps | Planned (4 plans, 3 waves) |
## Current Position ## Current Position
@@ -197,6 +197,7 @@ _Updated at each phase transition._
| Last session | 2026-05-30 — Phase 5 UAT: 5/6 tests passed; 3 gaps diagnosed (OneDrive unconfigured 500, cloud doc stream opaque 500, DropZone disappeared); gap-closure plan 05-12 created (3 tasks, wave 1) | | Last session | 2026-05-30 — Phase 5 UAT: 5/6 tests passed; 3 gaps diagnosed (OneDrive unconfigured 500, cloud doc stream opaque 500, DropZone disappeared); gap-closure plan 05-12 created (3 tasks, wave 1) |
| Last session | 2026-05-30 — Plan 05-12 executed: OAuth 400 preflight (unconfigured creds), 502 cloud fallback, celery-worker volume mount, upload hint in CloudStorageView; 293 passed / 24 xfailed / 1 pre-existing failure | | Last session | 2026-05-30 — Plan 05-12 executed: OAuth 400 preflight (unconfigured creds), 502 cloud fallback, celery-worker volume mount, upload hint in CloudStorageView; 293 passed / 24 xfailed / 1 pre-existing failure |
| Last session | 2026-05-30 — Phase 6.1 executed: 7 share tests + 4 audit tests promoted from xfail stubs; second_auth_user fixture added; 309 passed / 0 failed | | Last session | 2026-05-30 — Phase 6.1 executed: 7 share tests + 4 audit tests promoted from xfail stubs; second_auth_user fixture added; 309 passed / 0 failed |
| Next action | Run /gsd:verify-work 6.1 to confirm Phase 6.1 complete | | Last session | 2026-05-31 — Phase 6.2 planned: 4 plans (3 waves); SHARE-03/SHARE-05 (Plan 02), cloud-delete (Plan 03), ADMIN-06 audit enrichment + CSV + daily exports (Plan 04); verification passed (0 blockers, 2 cosmetic warnings fixed) |
| Next action | Run /gsd:execute-phase 6.2 |
| Pending decisions | None | | Pending decisions | None |
| Resume file | None | | Resume file | None |
@@ -12,10 +12,8 @@ files_modified:
- frontend/src/api/client.js - frontend/src/api/client.js
- backend/tests/test_documents.py - backend/tests/test_documents.py
autonomous: true autonomous: true
requirements: requirements: []
- SHARE-03 # cloud-delete (D-01..D-04) is covered via phase success criteria; no named REQUIREMENTS.md ID
- SHARE-05
- ADMIN-06
must_haves: must_haves:
truths: truths:
- "Deleting a cloud document calls the cloud provider's delete_object, not MinIO's" - "Deleting a cloud document calls the cloud provider's delete_object, not MinIO's"
@@ -601,7 +601,7 @@ return StreamingResponse(
--- ---
## Open Questions ## Open Questions (RESOLVED)
1. **Cloud delete partial failure: HTTP status code choice** 1. **Cloud delete partial failure: HTTP status code choice**
- What we know: D-03 says return a structured error the frontend can distinguish from hard failure. - What we know: D-03 says return a structured error the frontend can distinguish from hard failure.
@@ -0,0 +1,319 @@
---
phase: 6.2
slug: close-v1-sharing-cloud-delete-csv-export-gaps
status: draft
shadcn_initialized: false
preset: none
created: 2026-05-31
---
# Phase 6.2 — UI Design Contract
> Visual and interaction contract for Phase 6.2: Close v1 sharing + cloud-delete + CSV export gaps.
> Generated by gsd-ui-researcher. Verified by gsd-ui-checker.
---
## Design System
| Property | Value |
|----------|-------|
| Tool | none — pure Tailwind CSS v3.4 |
| Preset | not applicable |
| Component library | none (Heroicons inline SVG only) |
| Icon library | Heroicons stroke, w-4 / w-5 sizes (inline SVG, no package) |
| Font | system-ui (browser default stack — no custom font loaded) |
**Source:** codebase scan — no `components.json`, no component registry, confirmed via directory listing.
---
## Spacing Scale
Declared values (all multiples of 4, mapped to Tailwind utilities):
| Token | Value | Tailwind Class | Usage |
|-------|-------|----------------|-------|
| xs | 4px | `gap-1`, `p-1` | Icon gaps, inline badge padding |
| sm | 8px | `gap-2`, `p-2` | Compact element spacing, button icon padding |
| md | 16px | `p-4`, `gap-4` | Default element spacing, card body padding |
| lg | 24px | `p-6`, `gap-6` | Modal body padding, section padding |
| xl | 32px | `p-8` | Empty state vertical padding |
| 2xl | 48px | `py-12` | Page-level empty state vertical rhythm |
| 3xl | 64px | n/a | Not used in this phase |
**Exceptions:**
- Icon-only action buttons: `min-h-[44px] min-w-[44px]` touch target — established in DocumentCard and maintained for all new icon buttons. Source: `DocumentCard.vue` line 43.
- Share row inline items: `py-2` (8px vertical) per row — matches existing `ShareModal.vue` recipient list rhythm.
- Permission dropdown in share creation row: no extra spacing; sits inline within the existing `flex gap-2` row.
---
## Typography
| Role | Size | Weight | Line Height | Tailwind Classes |
|------|------|--------|-------------|------------------|
| Body | 14px | 400 | 1.5 | `text-sm` |
| Label / caption | 12px | 600 | 1.4 | `text-xs font-semibold` |
| Heading (modal title) | 18px | 600 | 1.2 | `text-lg font-semibold` |
| Mono (timestamps, IDs) | 12px | 400 | 1.4 | `text-xs font-mono` |
**Source:** codebase scan — `ShareModal.vue` uses `text-lg font-semibold` for modal title, `text-sm` for body text, `text-xs` for labels and badges. `AuditLogTab.vue` uses `font-mono text-xs` for timestamps and IP addresses. All four roles are already present in the components being modified.
---
## Color
| Role | Value | Tailwind Class | Usage |
|------|-------|----------------|-------|
| Dominant (60%) | #F9FAFB | `bg-gray-50` | Page background, table header row |
| Secondary (30%) | #FFFFFF | `bg-white` | Cards, modals, table body, dropdown panels |
| Accent (10%) | #4F46E5 | `bg-indigo-600` / `text-indigo-600` | Primary action buttons, focus rings, topic pills, shared badge |
| Destructive | #EF4444 | `text-red-500` / `text-red-600` / `bg-red-50` | Remove access button, cloud delete failure modal warning text, error inline text |
**Accent reserved for (explicit list):**
1. Primary CTA buttons: "Share document", "Apply filters", "Export CSV", "Download" — `bg-indigo-600 hover:bg-indigo-700 text-white`
2. Focus rings on all text inputs and selects: `focus:ring-2 focus:ring-indigo-500`
3. "Shared" indicator pill on DocumentCard: `bg-indigo-50 text-indigo-600`
4. Permission badge when set to "edit" (distinguished from "view" gray): `bg-indigo-50 text-indigo-600` — matches topic pill pattern
5. View/Edit toggle active state: `bg-indigo-50 text-indigo-600`
**Destructive reserved for:**
1. "Remove access" inline link in ShareModal recipient row — `text-red-500 hover:text-red-700`
2. Cloud delete failure modal — warning message text `text-red-700`, border accent on the modal (see Component Contracts below)
3. Inline error text under inputs — `text-red-600 text-xs`
**Source:** codebase scan — colors extracted from `ShareModal.vue`, `DocumentCard.vue`, `AuditLogTab.vue`, `AdminUsersTab.vue`.
---
## Copywriting Contract
### Permission Dropdown (ShareModal — share creation row)
| Element | Copy |
|---------|------|
| Dropdown label (aria-label) | "Permission level" |
| Option: view | "Can view" |
| Option: edit | "Can edit" |
| Default selected | "Can view" |
### View/Edit Toggle (ShareModal — per share row)
| Element | Copy |
|---------|------|
| Toggle label: view active | "View" |
| Toggle label: edit active | "Edit" |
| Aria-label pattern | "Change permission for {handle}" |
| Optimistic error (toggle fails) | "Failed to update permission." |
### Cloud Delete Failure Modal
| Element | Copy |
|---------|------|
| Modal heading | "Cloud delete failed" |
| Body text | "The file could not be deleted from {provider}. Remove it from DocuVault anyway? The file will remain on {provider}." |
| Primary CTA (remove from app) | "Remove from app" |
| Secondary action (cancel) | "Cancel" |
| Aria-label for modal | "Cloud delete warning" |
**Note:** `{provider}` is replaced at runtime with the cloud provider display name (e.g., "Google Drive", "OneDrive"). If the provider name is unavailable, fall back to "your cloud storage".
### Audit Log — Daily Exports Section
| Element | Copy |
|---------|------|
| Section label | "Daily exports" |
| Dropdown label | "Select date" |
| Dropdown placeholder | "Choose a date" |
| Download button | "Download" |
| Empty state (no exports in bucket) | "No daily exports available." |
| Loading state (fetching list) | "Loading exports…" |
### Audit Log — CSV Export Fix (behavior only, no copy change)
The "Export CSV" button label is unchanged. The behavior changes from `window.location.href` to `fetch()` + Blob URL. No new copy needed — the button already reads "Export CSV".
### Audit Log — User Filter Label
| Element | Copy |
|---------|------|
| Filter field label (was "User") | "User handle" |
| Input placeholder (was "All users") | "All users" |
### Shared Badge Fix (DocumentCard — no copy change)
The "Shared" pill copy is unchanged (`Shared`). Only the v-if condition changes from `doc.share_count > 0` to `doc.is_shared`. No copy update.
### Error States
| Scenario | Copy |
|----------|------|
| Share creation — user not found | "User not found. Check the handle and try again." (unchanged, already in ShareModal) |
| Share creation — already shared | "This document is already shared with that user." (unchanged) |
| Share creation — generic error | "Something went wrong. Please try again." (unchanged) |
| Permission update failed | "Failed to update permission." |
| Daily export download failed | "Download failed. Please try again." |
| CSV export request failed | "Export failed. Please try again." |
| Cloud delete failure | See modal copy above. |
### Destructive Actions
| Action | Confirmation Approach |
|--------|-----------------------|
| Remove access (revoke share) | Optimistic — immediate removal on click, restore on API failure. No confirmation dialog. Matches existing pattern in `ShareModal.vue:handleRevoke()`. |
| Delete cloud document (default) | Browser `window.confirm()` replaced by the cloud delete failure modal only when the API returns `{"cloud_delete_failed": true}`. Normal delete (MinIO or successful cloud delete) keeps the existing `window.confirm()` on `DocumentView.vue`. |
| "Remove from app" (cloud delete failure path) | Confirmed via the cloud delete failure modal primary CTA. Single click on "Remove from app" proceeds without a second confirmation. |
---
## Component Contracts
### C-1: Permission Dropdown in ShareModal (share creation row)
**Location:** `frontend/src/components/sharing/ShareModal.vue` — insert between the handle input and the "Share document" button within the `flex gap-2` row.
**Markup contract:**
```
<select
v-model="permission"
aria-label="Permission level"
class="border border-gray-300 rounded-lg px-3 py-2 text-sm bg-white focus:outline-none focus:ring-2 focus:ring-indigo-500 shrink-0"
>
<option value="view">Can view</option>
<option value="edit">Can edit</option>
</select>
```
- `permission` reactive ref defaults to `"view"`
- Passed as `permission: permission.value` in the `shareDocument()` call
- Width: `shrink-0` — does not expand; native select width for 2 options is sufficient (~90px)
- Sits between handle input (`flex-1`) and Share button (`shrink-0`)
### C-2: View/Edit Toggle in ShareModal (per share row)
**Location:** `frontend/src/components/sharing/ShareModal.vue` — replace the static `<span class="text-xs bg-gray-100 text-gray-600 px-2 py-1 rounded-full font-medium">view</span>` in each recipient row.
**Visual spec:**
- Two adjacent pill buttons: "View" and "Edit"
- Active state: `bg-indigo-50 text-indigo-600 font-medium`
- Inactive state: `bg-gray-100 text-gray-600`
- Both use: `text-xs px-2 py-1 rounded-full font-medium transition-colors`
- Wrapper: `flex rounded-full overflow-hidden border border-gray-200` (pill group container)
- Spacing: no gap between the two buttons (joined pills)
**Interaction:**
- Clicking the inactive state calls `PATCH /api/shares/{id}` with `{ permission: "view" | "edit" }`
- Optimistic update: toggle state immediately on click, revert on API error
- Loading state: button shows spinner inline while PATCH is in-flight; both toggle buttons `opacity-50 pointer-events-none` during in-flight state
- Error: show `error.value = "Failed to update permission."` below the row (same `text-xs text-red-600 mt-2` pattern)
### C-3: Cloud Delete Failure Modal
**Location:** New component `frontend/src/components/documents/CloudDeleteWarningModal.vue` OR inline conditional block in `DocumentView.vue` — inline in DocumentView is preferred (mirrors how the inline delete confirmation panel works in AdminUsersTab).
**Trigger:** `confirmDelete()` in `DocumentView.vue` receives `{ cloud_delete_failed: true }` from the delete API call. Instead of navigating away, set `showCloudDeleteWarning.value = true`.
**Visual spec:**
```
Fixed overlay: bg-black/40 flex items-center justify-center z-50
Panel: bg-white rounded-2xl shadow-xl p-6 max-w-sm w-full mx-4
```
- Heading: `text-lg font-semibold text-gray-900 mb-2` — "Cloud delete failed"
- Body: `text-sm text-gray-600 mb-6` — full warning sentence (see Copywriting Contract)
- Warning icon: Heroicons `ExclamationTriangleIcon` w-5 h-5 text-amber-500 inline before heading, or `text-red-600` — use amber-500 (warning, not error) to match the semantic distinction: this is a degraded-success, not a hard failure
- Button row: `flex gap-3 mt-4 justify-end`
- Primary ("Remove from app"): `bg-red-600 hover:bg-red-700 text-white text-sm px-4 py-2 rounded-lg transition-colors`
- Secondary ("Cancel"): `border border-gray-300 text-gray-700 text-sm px-4 py-2 rounded-lg hover:bg-gray-50 transition-colors`
- `role="dialog"` `aria-modal="true"` `aria-labelledby` on the panel
- Click-outside (`@click.self`) closes the modal (same as ShareModal)
- Pressing "Cancel" closes modal; document is NOT deleted. The pending delete is abandoned.
- Pressing "Remove from app" calls `DELETE /api/documents/{id}?remove_only=true`, then navigates to `/` on success.
**Warning icon Tailwind:** `text-amber-500` (Tailwind `amber-500` = `#F59E0B`) — signals a recoverable warning state distinct from the hard red error color.
### C-4: Daily Exports Section in AuditLogTab
**Location:** `frontend/src/components/admin/AuditLogTab.vue` — add as a new section below the existing pagination block.
**Visual spec:**
- Section separator: `<div class="border-t border-gray-100 mt-6 pt-6">`
- Section label: `<h3 class="text-sm font-semibold text-gray-700 mb-3">Daily exports</h3>`
- Controls row: `<div class="flex items-end gap-3">`
- Date select: `<select class="text-sm border border-gray-300 rounded-lg px-3 py-2 focus:outline-none focus:ring-2 focus:ring-indigo-500 bg-white">` populated from API response
- Download button: `<button class="bg-indigo-600 hover:bg-indigo-700 text-white text-sm px-4 py-2 rounded-lg disabled:opacity-50 transition-colors">Download</button>`
- Button disabled when no date is selected or download is in-flight
- Loading state (fetching list): `<p class="text-sm text-gray-400">Loading exports…</p>` replaces the select
- Empty state (bucket empty): `<p class="text-sm text-gray-400 italic">No daily exports available.</p>` replaces the select
- Download in-flight: spinner inline in Download button (same `animate-spin` pattern as ShareModal submit button)
**Date option format in select:** `YYYY-MM-DD` as displayed value (e.g., "2026-05-30"). Options sorted newest-first. `value` attribute is the date string (used to construct the API call).
### C-5: Audit Log User Filter Label Update
**Location:** `frontend/src/components/admin/AuditLogTab.vue` — filter bar.
- Change `<label>` text from "User" to "User handle"
- Change `v-model` binding from `filters.user_id` to `filters.user_handle` (or rename the reactive property)
- Placeholder stays "All users"
- No visual change otherwise; same `text-sm border border-gray-300 rounded-lg px-3 py-2` input styling
---
## State Inventory
Every interactive element in this phase must handle these states:
| Component | States Required |
|-----------|----------------|
| Permission dropdown (C-1) | default (view), changed (edit), disabled (during submit) |
| View/Edit toggle (C-2) | view-active, edit-active, loading (in-flight PATCH), error |
| Cloud delete warning modal (C-3) | hidden, visible, removing (in-flight remove_only call) |
| Daily exports select (C-4) | loading, empty, populated, selection-made |
| Daily exports download button (C-4) | default, disabled (no selection), loading (in-flight download), error |
| CSV export button | default, loading (in-flight fetch), error |
| Share creation button | default, disabled (empty handle), loading, error |
---
## Accessibility Contract
- All new interactive elements have an `aria-label` or are labelled by a visible `<label>` element
- Cloud delete failure modal: `role="dialog"` `aria-modal="true"` `aria-labelledby="cloud-delete-modal-title"` on the panel; focus trapped within modal while open
- View/Edit toggle buttons: each `<button>` has `aria-pressed` reflecting the current active state; wrapper has `role="group"` with `aria-label="Permission"`
- Permission dropdown: `aria-label="Permission level"` (no visible label needed — sits inline)
- All destructive buttons use `text-red-600` or `bg-red-600` and include descriptive accessible names
- Minimum touch target `min-h-[44px] min-w-[44px]` applied to all icon-only buttons; inline text buttons (e.g., "Remove access", "Cancel") do not require the 44px minimum
---
## Registry Safety
| Registry | Blocks Used | Safety Gate |
|----------|-------------|-------------|
| shadcn official | none | not applicable — shadcn not initialized |
| Third-party | none | not applicable |
No component library. No registry. All components hand-built with Tailwind CSS following established project patterns.
---
## 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
**Approval:** pending