nexus/.planning/phases/26-pwa-performance/26-UI-SPEC.md

12 KiB
Raw Blame History

phase slug status shadcn_initialized preset created
26 pwa-performance draft true new-york / neutral / cssVariables 2026-04-01

Phase 26 — UI Design Contract

Visual and interaction contract for Phase 26: PWA & Performance. Generated by gsd-ui-researcher, verified by gsd-ui-checker.


Design System

Property Value
Tool shadcn (new-york style)
Preset new-york, neutral base color, cssVariables: true
Component library Radix UI (via shadcn)
Icon library lucide-react
Font System default (no custom web font — performance constraint)

Source: ui/components.json, ui/vite.config.ts


Existing PWA Infrastructure (Pre-Phase)

The following already exists and must NOT be re-implemented — Phase 26 extends it:

Asset Path Current State
Service worker ui/public/sw.js Network-first, cache-as-fallback, skips /api
Web manifest ui/public/site.webmanifest Standalone display, Nexus name, two PNG icon sizes
Viewport meta ui/index.html viewport-fit=cover, user-scalable=no, maximum-scale=1
Apple PWA meta ui/index.html apple-mobile-web-app-capable, status bar style, title
Theme-color meta ui/index.html Dynamic per-theme via inline script
SW registration ui/src/main.tsx Registered on load event
Icons (existing) ui/public/ android-chrome-192x192.png, android-chrome-512x512.png, apple-touch-icon.png, favicons

Phase 26 upgrades the SW to a proper cache-first strategy for static assets, adds offline message queuing, adds the install prompt UI, adds push notification wiring, and completes the responsive mobile layout.


Spacing Scale

Declared values (multiples of 4):

Token Value Usage
xs 4px Icon gaps, inline chip padding
sm 8px Compact element spacing, icon button padding
md 16px Default element spacing, input padding
lg 24px Section padding, card padding
xl 32px Layout gaps
2xl 48px Major section breaks
3xl 64px Page-level spacing

Exceptions:

  • Touch targets: minimum 44px × 44px on all interactive elements (mobile). Applies to: conversation list items, message action buttons, pull-to-refresh drag zone, install prompt dismiss button, notification permission button.
  • iOS safe area insets: input bar bottom padding must use env(safe-area-inset-bottom) on mobile to avoid the home indicator. Apply as pb-[env(safe-area-inset-bottom)] or equivalent.
  • Keyboard-aware resize: when the virtual keyboard appears, the sticky input bar must remain visible above it. This is handled via 100dvh viewport height (dynamic viewport unit) rather than 100vh.

Typography

Role Size Weight Line Height
Body 14px 400 1.5
Label 12px 400 1.3
Heading 16px 600 1.2
Display 20px 600 1.2

Source: Matches existing chat component patterns (ChatPanel, ChatInput, ChatConversationList use text-sm/text-xs/text-base/font-semibold).

No new font sizes are introduced in Phase 26. All new UI elements (install prompt, offline banner, notification permission prompt) use the existing Body/Label scale.


Color

Role Value Usage
Dominant (60%) var(--background) App shell, chat background
Secondary (30%) var(--card) / var(--sidebar) Sidebar, cards, input surface, overlays
Accent (10%) var(--primary) Install prompt CTA button, notification permission accept button
Destructive var(--destructive) Offline banner error state, notification permission deny action

Theme-aware values:

  • Catppuccin Mocha (dark default): background #1e1e2e, card #181825, primary #89b4fa
  • Tokyo Night (dark): background #1a1b26, card #16161e, primary #7aa2f7
  • Catppuccin Latte (light): background #eff1f5, card #e6e9ef, primary #1e66f5

Accent reserved for:

  1. "Add to Home Screen" / "Install app" CTA button in the install prompt banner
  2. "Allow notifications" accept button in the push notification permission prompt
  3. Offline queue flush indicator (spinner/dot when messages are queued and reconnection is in progress)

Offline banner uses a dedicated amber/warning surface:

  • Dark themes: bg-amber-900/40 text-amber-200 border border-amber-800
  • Light theme: bg-amber-50 text-amber-800 border border-amber-200 (These are utility classes, not new CSS variables — keeps the token count stable.)

Layout Breakpoints

Breakpoint Viewport Chat Layout
Mobile < 768px (below Tailwind md) Full-screen chat replaces desktop slide-in panel; sidebar becomes a bottom sheet or full-screen overlay; input bar sticky at bottom
Tablet 768px1024px Split view: sidebar at 280px + chat panel fills remainder
Desktop > 1024px Existing layout: hidden md:flex chat panel at fixed width, sidebar pinned

The existing hidden md:flex class on ChatPanel means the panel is invisible on mobile. Phase 26 adds a mobile-first full-screen chat view accessible via a bottom navigation tap or a floating action button.

Mobile chat layout:

  • Header: 48px tall, contains back button (← with aria-label="Back to conversations"), conversation title (truncated), agent selector icon
  • Message list: fills remaining viewport height using h-[calc(100dvh-48px-56px-env(safe-area-inset-bottom))]
  • Input bar: 56px minimum, sticky bottom-0, padded by env(safe-area-inset-bottom)

Conversation list on mobile:

  • Full-screen overlay or bottom sheet (Sheet component from shadcn)
  • Pull-to-refresh drag zone: 60px from top, visual indicator using a spinner at --primary color
  • Each conversation list item: minimum 48px tall touch target

Component Inventory (New for Phase 26)

Component Type Description
InstallPromptBanner New Top or bottom banner shown after beforeinstallprompt; contains "Add to Home Screen" CTA + dismiss
OfflineBanner New Amber banner shown when navigator.onLine is false; shows queued message count
NotificationPermissionPrompt New Modal or inline prompt requesting push notification permission; two buttons: Allow + Not now
PullToRefresh New Wraps conversation list; detects swipe-down gesture and triggers refetch
MobileChatView New Full-screen mobile chat layout (header + message list + sticky input)
MobileNavBar New Bottom navigation bar for mobile: Dashboard, Chat, Inbox tabs (minimum 44px tap height)
OfflineMessageQueue Logic only Hook useOfflineQueue — no visual output; stores unsent messages in IndexedDB, flushes on reconnect

Existing components modified in Phase 26:

Component Change
ChatPanel Wrap with responsive logic; show MobileChatView on mobile, existing panel on desktop
ChatConversationList Wrap with PullToRefresh
ChatInput Add pb-[env(safe-area-inset-bottom)] safe area padding; ensure 44px minimum tap height on Send button

Copywriting Contract

Element Copy
Install prompt heading "Add Nexus to your home screen"
Install prompt body "Get the full experience — launch instantly, works offline."
Install prompt CTA "Add to Home Screen"
Install prompt dismiss "Not now"
Offline banner "You're offline — messages will send when you reconnect"
Offline banner with queue "You're offline — {n} message{s} queued"
Notification permission heading "Stay in the loop"
Notification permission body "Get notified when your agents complete tasks or need input."
Notification permission accept "Allow notifications"
Notification permission deny "Not now"
Pull-to-refresh loading (spinner only, no text)
Pull-to-refresh release "Release to refresh"
Pull-to-refresh pulling "Pull to refresh"
Empty offline state "No conversations yet" / "Start a conversation to get going."
Cached load label (no user-visible label — load is silent)
Push notification title (mention) "Nexus — {AgentName} mentioned you"
Push notification body (mention) "{snippet of message, max 80 chars}"
Push notification title (task complete) "Nexus — Task completed"
Push notification body (task complete) "{AgentName} completed "{TaskTitle}""
Push notification title (handoff) "Nexus — Handoff request"
Push notification body (handoff) "{AgentName} is ready with a spec for you."
Destructive: revoke notifications "Turn off notifications": "You can re-enable them any time in your browser settings."

Destructive actions in Phase 26:

  • None requiring a confirmation dialog. Notification permission denial is non-destructive (browser handles revoking via settings). No data deletion in this phase.

Interaction States

Install Prompt Banner

  • Shown: once per session, after beforeinstallprompt fires AND user has visited at least one conversation (not shown on first-ever load before any engagement)
  • Dismissed: stored in localStorage key nexus.installPromptDismissed; not shown again for 7 days
  • Already installed: banner never shown (detect via window.matchMedia('(display-mode: standalone)'))
  • Position: fixed bottom, above the input bar on mobile; fixed top on desktop

Offline Banner

  • Shown: when navigator.onLine === false
  • Hidden: auto-dismisses 3 seconds after navigator.onLine returns true and the queue is empty
  • Persistent while offline: does not auto-dismiss while still offline

Notification Permission Prompt

  • Shown: after user's third agent response in any conversation (engagement gate)
  • Never shown if Notification.permission === 'granted' or === 'denied'
  • Stored dismissed state: localStorage key nexus.notifPromptDismissed

Pull-to-Refresh

  • Trigger threshold: 64px downward drag from top of conversation list
  • Visual: spinner using var(--primary) color, 24px, appears at the pull origin point
  • Max overdrag: 96px before triggering (prevents accidental trigger)
  • Haptic feedback: navigator.vibrate(10) on trigger if available

Mobile Chat Navigation

  • Bottom nav is always visible on mobile (< 768px) when the app is installed (standalone) or on a touch device
  • Active tab uses accent color (var(--primary)) for icon tint
  • Inactive tabs use var(--muted-foreground) for icon tint

Performance Targets (Visual-Side Contracts)

Metric Target Implementation Contract
Initial load (broadband) < 2s Vite code-split per route; no blocking scripts; no web fonts
Initial load (3G) < 5s Vite code-split; compress assets; inline critical CSS
PWA cached load < 1s SW cache-first for all static assets (JS/CSS/HTML); stale-while-revalidate for fonts/images
Shell paint < 200ms App shell (HTML/CSS) cached by SW; no API calls blocking shell render
Offline UI load < 1s All static assets served from SW cache; API failures handled gracefully

SW cache strategy:

  • Cache name: nexus-v1 (rename from existing paperclip-v2 to bust stale cache)
  • Static assets (JS, CSS, fonts, images): cache-first with network fallback
  • Navigation requests (/): cache-first (app shell)
  • API requests (/api/*): network-only; failures trigger offline queue for POST/mutations

Registry Safety

Registry Blocks Used Safety Gate
shadcn official button, sheet, dialog, badge, skeleton, tooltip not required
none (third-party) not applicable

No third-party registries are declared for Phase 26.


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