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:
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
|
||||
Reference in New Issue
Block a user