Files
curo1305andClaude Sonnet 4.6 fe54a855b3 docs(phase-12): fix UI-SPEC typography and spacing blocking issues
- Collapse font weights from 3 to 2: drop font-medium (500), use font-semibold (600) for row primary names, labels, column headers; font-normal (400) for body
- Fix sm spacing token usage: replace p-1.5 (6px) with p-2 (8px) to match declared 8px value
- Update health badge in connection root list from font-medium to font-semibold

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-18 22:19:25 +02:00

267 lines
12 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
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-2`, `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 (`font-normal`) | 1.5 |
| Label / metadata | 12px (`text-xs`) | 600 semibold (`font-semibold`) | 1.4 |
| Row primary name | 14px (`text-sm`) | 600 semibold (`font-semibold`) | 1.5 |
| Column header | 12px (`text-xs`) | 600 semibold, `uppercase tracking-wider` | 1.4 |
Two weights only: `font-normal` (400) for body, `font-semibold` (600) for all labels, metadata, column headers, and row primary names. `font-medium` (500) is not used.
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-semibold 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