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

12 KiB
Raw Permalink Blame History

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 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