docs(phase-12): add UI design contract for cloud-resource-foundation

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
curo1305
2026-06-18 22:16:22 +02:00
co-authored by Claude Sonnet 4.6
parent 52b110acef
commit ea682fdecd
@@ -0,0 +1,264 @@
---
phase: 12
slug: cloud-resource-foundation
status: draft
shadcn_initialized: false
preset: none
created: 2026-06-18
---
# Phase 12 — UI Design Contract
> Visual and interaction contract for Phase 12: cloud-resource-foundation.
> Generated by gsd-ui-researcher, verified by gsd-ui-checker.
---
## Design System
| Property | Value |
|----------|-------|
| Tool | none — manual Tailwind CSS |
| Preset | not applicable |
| Component library | none (Vue 3 custom components) |
| Icon library | AppIcon.vue (Heroicons-based, internal wrapper) |
| Font | system-ui / Inter (Tailwind default sans stack) |
No `components.json` was found. The project uses manual Tailwind CSS v3 with `@tailwindcss/forms`. No shadcn initialization is needed or applicable. All new UI must match the existing `StorageBrowser.vue` component patterns.
---
## Spacing Scale
Declared values (must be multiples of 4):
| Token | Value | Usage |
|-------|-------|-------|
| xs | 4px | Icon gaps, inline badge padding (`gap-1`, `px-1`) |
| sm | 8px | Compact element spacing, toolbar icon buttons (`p-1.5`, `gap-2`) |
| md | 16px | Default element spacing, row padding (`px-4`) |
| lg | 24px | Section padding, horizontal gutters (`px-6`) |
| xl | 32px | Layout gaps |
| 2xl | 48px | Major section breaks |
| 3xl | 64px | Page-level spacing |
Exceptions:
- Touch target minimum: 36×36px (`min-w-[36px] min-h-[36px]`) for row action buttons on mobile — matches existing `StorageBrowser.vue` pattern.
- Sync indicator: inline next to breadcrumb or toolbar, 20×20px icon (`w-5 h-5`), no extra surrounding padding.
---
## Typography
| Role | Size | Weight | Line Height |
|------|------|--------|-------------|
| Body | 14px (`text-sm`) | 400 regular | 1.5 |
| Label / metadata | 12px (`text-xs`) | 600 semibold (`font-semibold`) | 1.4 |
| Row primary name | 14px (`text-sm`) | 500 medium (`font-medium`) | 1.5 |
| Column header | 12px (`text-xs`) | 600 semibold, `uppercase tracking-wider` | 1.4 |
Source: extracted from `StorageBrowser.vue` — do not deviate without updating this contract.
---
## Color
| Role | Tailwind Token | Usage |
|------|----------------|-------|
| Dominant (60%) | `white` / `bg-white` | Page background, sticky toolbar, row base |
| Secondary (30%) | `gray-50` / `gray-100` | List header row, alternating hover state, sidebar |
| Accent (10%) | `indigo-500` / `indigo-600` | See reserved list below |
| Warning | `amber-400` / `amber-500` | Folder icons, new-folder input border |
| Destructive | `red-500` / `red-50` | Delete action hover, error text |
**Accent (`indigo`) reserved exclusively for:**
- Primary action buttons (e.g., "Connect account", "Browse")
- Focus rings (`focus-visible:ring-indigo-500`) on all interactive elements
- Active/selected navigation items
- Inline text links and "Clear search" calls-to-action
- Connection root icon in breadcrumb bar
**Do NOT use indigo for:**
- Disabled/unsupported action controls (use `gray-400` text on `gray-100` background)
- Temporary-failure warning state (use `amber-600` text on `amber-50` background)
- Metadata labels or column headers
### New semantic colors for Phase 12
| Semantic role | Tailwind token | Applied to |
|---------------|----------------|------------|
| Unsupported (permanent) | `text-gray-400 bg-gray-100` | Disabled action buttons for structurally unsupported operations |
| Temporarily blocked | `text-amber-600 bg-amber-50 border-amber-200` | Disabled action buttons for recoverable failures (scope, auth, offline) |
| Refreshing | `text-indigo-400` (animated) | Breadcrumb sync spinner |
| Up to date | `text-green-500` | Breadcrumb sync indicator after successful refresh |
| Connection warning | `text-amber-500` | Breadcrumb sync indicator on refresh failure |
Source: D-09 from CONTEXT.md — grey = structurally unsupported; amber/warning = temporarily blocked.
---
## Component Inventory
These components are extended or introduced in Phase 12. All must follow existing architecture rules: views are thin data providers; smart components receive props and emit actions.
### Extended: `StorageBrowser.vue`
**New props (data-driven):**
| Prop | Type | Description |
|------|------|-------------|
| `capabilities` | `Record<ActionKey, CapabilityState>` | Map of action name to `{ state: 'supported' \| 'unsupported' \| 'temporarily_unavailable', message: string, reasonCode: string }` |
| `folderFreshness` | `'fresh' \| 'refreshing' \| 'warning'` | Current sync state of the displayed folder |
| `lastRefreshedAt` | `string \| null` | ISO timestamp for tooltip on sync indicator |
| `connectionRoot` | `{ id: string, displayName: string, providerType: string }` | Identity of the current connection root |
| `byteAvailability` | `Record<string, 'cloud_only' \| 'cached'>` | Per-item byte state keyed by DocuVault item UUID |
**Existing `mode` prop behavior change:** replace `mode === 'local'` action-hiding with capability rendering. Local mode supplies a full-supported capability set; cloud mode supplies provider-normalized capabilities. One rendering path serves both.
**New emitted events:**
| Event | Payload | Description |
|-------|---------|-------------|
| `capability-explain` | `{ actionKey: string, message: string }` | User tapped/focused a disabled control on touch |
### Extended: `BreadcrumbBar.vue`
Adds a sync state indicator immediately after the last breadcrumb segment:
- Refreshing: `AppSpinner` sized 16px (`w-4 h-4`) in `text-indigo-400`
- Up to date: checkmark icon 16px in `text-green-500`, fades out after 3 s
- Connection warning: warning triangle icon 16px in `text-amber-500`, persists
Tooltip on the indicator (desktop hover, touch tap): `"Last updated [relative time]"` or `"Refresh failed — retrying"`.
The breadcrumb root segment shows the connection display name and a small provider/cloud icon (16px) before it. No separate `Cloud` prefix segment.
### New: Disabled action button pattern
Unsupported and temporarily-blocked actions render as `<button>` elements with `aria-disabled="true"` and no native `disabled` attribute. This ensures they remain focusable and tappable for explanation delivery.
Unsupported (permanent):
```
text-gray-400 bg-transparent cursor-not-allowed
aria-disabled="true"
```
Temporarily blocked:
```
text-amber-600 bg-amber-50 border border-amber-200 cursor-not-allowed
aria-disabled="true"
```
On click/tap while `aria-disabled="true"`: emit `capability-explain` with the action's message. The parent view displays the explanation in a tooltip (desktop) or an inline dismissable notice below the toolbar (mobile touch).
Tooltip/notice format: plain sentence, e.g. `"Rename is not supported by this server"` or `"Reconnect with write access to enable uploads"`.
### Connection display name disambiguation
When a connection's `displayName` equals the plain provider label (e.g. `OneDrive`) and another connection shares the same default name, append the connection UUID's first 4 characters in `text-gray-400 text-xs` after the name: `OneDrive · a3f2`. This clears once the user renames either connection. Source: D-03 from CONTEXT.md.
### Byte availability indicator
Per-row compact icon at the far right of file rows (folders have no indicator):
- `cloud_only`: no marker (absence is the default state — no visual noise)
- `cached`: download/cache icon 14px (`w-3.5 h-3.5`) in `text-indigo-400`; tooltip reads `"Bytes cached in DocuVault storage — counts toward your quota"`
Source: D-17 from CONTEXT.md.
---
## Interaction Contracts
### Capability-disabled controls (CLOUD-08)
| Trigger | Behavior |
|---------|----------|
| Desktop hover over `aria-disabled` button | Show tooltip with explanation message |
| Keyboard focus on `aria-disabled` button | Show tooltip with explanation message |
| Touch tap on `aria-disabled` button | Show inline dismissable notice below toolbar; suppress action |
| Click on active (enabled) button | Emit action normally |
Never use native `disabled` attribute — disabled buttons do not receive focus or click events, which breaks touch explanation delivery. Source: RESEARCH.md planning risk #7.
### Folder freshness / background reconciliation (SYNC-01, D-11, D-12)
| State | Breadcrumb indicator | Row behavior |
|-------|----------------------|-------------|
| `refreshing` | Spinner (indigo-400, 16px, animated) | Rows remain interactive; no skeleton overlay |
| `fresh` | Checkmark (green-500, 16px, fades after 3 s) | Normal |
| `warning` | Warning triangle (amber-500, 16px, persistent) | Rows remain interactive; connection warning banner shown below toolbar |
Connection warning banner (shown when `folderFreshness === 'warning'`):
- Background: `bg-amber-50 border-b border-amber-200`
- Text: `text-amber-800 text-sm``"Could not refresh — showing cached results from [relative time]. Retrying automatically."`
- No dismiss button; disappears when state returns to `refreshing` or `fresh`
- Source: D-14 from CONTEXT.md.
### Connection root list (Cloud Storage landing)
- Displays one row per connected account using the same `StorageBrowser` grid
- Provider icon (24px) + connection display name + connection health badge
- Health badge: `text-green-700 bg-green-50 rounded-full text-xs font-medium px-2 py-0.5` for connected; `text-amber-700 bg-amber-50` for warning; `text-red-700 bg-red-50` for failed
- Clicking a connection root navigates into that connection using connection UUID in the route
### Session-scoped last-visited folder (D-05)
Last visited folder is stored in `sessionStorage` only, keyed by `connectionId`. On fresh tab/session, navigation starts at the connection root. No persistent state.
---
## Copywriting Contract
| Element | Copy |
|---------|------|
| Primary CTA (connection root list, no connections) | "Connect cloud storage" |
| Primary CTA (toolbar, enter connection) | "Browse files" |
| Empty state heading (no connections yet) | "No cloud storage connected" |
| Empty state body (no connections yet) | "Connect OneDrive, Google Drive, Nextcloud, or a WebDAV server to browse your files here." |
| Empty state heading (empty folder) | "This folder is empty" |
| Empty state body (empty folder) | "Files and folders you add to this location in [provider name] will appear here." |
| Error state — refresh failure | "Could not refresh — showing cached results from [relative time]. Retrying automatically." |
| Error state — connection offline | "Could not reach [connection display name]. Check your connection or reconnect your account." |
| Unsupported action tooltip — permanent | "[Action] is not supported by this server" (e.g. "Rename is not supported by this server") |
| Temporarily blocked tooltip — insufficient scope | "Reconnect with write access to enable [action]" |
| Temporarily blocked tooltip — read-only | "This connection is read-only" |
| Byte cache tooltip | "Bytes cached in DocuVault storage — counts toward your quota" |
| Sync indicator tooltip — up to date | "Last updated [relative time]" |
| Sync indicator tooltip — warning | "Refresh failed — retrying" |
| Connection name disambiguation suffix | "[Display name] · [4-char UUID prefix]" |
No destructive actions in Phase 12 scope. Delete (CLOUD-07) is Phase 13.
---
## Accessibility Contract
- All interactive elements must have `focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-indigo-500 focus-visible:ring-offset-1` (matching existing pattern in `StorageBrowser.vue`).
- `aria-disabled="true"` controls must be keyboard-reachable and respond to Enter/Space with the explanation, not the action.
- Sync indicator must have `aria-label` reflecting current state: `"Refreshing folder"`, `"Folder up to date"`, or `"Folder refresh failed"`.
- Provider/cloud icon in breadcrumb must have `aria-hidden="true"` (decorative).
- Byte availability icon must have `title` attribute matching tooltip copy.
- Minimum touch target: 36×36px for all action buttons.
---
## Registry Safety
| Registry | Blocks Used | Safety Gate |
|----------|-------------|-------------|
| shadcn official | none — not initialized | not applicable |
| Third-party | none | not applicable |
No third-party component registries are used. All components are hand-authored Tailwind Vue SFCs following existing 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