- 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>
12 KiB
phase, slug, status, shadcn_initialized, preset, created
| phase | slug | status | shadcn_initialized | preset | created |
|---|---|---|---|---|---|
| 12 | cloud-resource-foundation | draft | false | none | 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 existingStorageBrowser.vuepattern. - 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-400text ongray-100background) - Temporary-failure warning state (use
amber-600text onamber-50background) - 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:
AppSpinnersized 16px (w-4 h-4) intext-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) intext-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
refreshingorfresh - Source: D-14 from CONTEXT.md.
Connection root list (Cloud Storage landing)
- Displays one row per connected account using the same
StorageBrowsergrid - 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.5for connected;text-amber-700 bg-amber-50for warning;text-red-700 bg-red-50for 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 inStorageBrowser.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-labelreflecting 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
titleattribute 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