learning_ai_clock/docs/UX_TESTING_SETUP_GUIDE.md
root ce7e04a158 feat(ux): add UX testing setup guide and common platform integration
- Add UX_TESTING_SETUP_GUIDE.md for ChronoMind
- Create .pnpmfile.cjs for local package resolution
- Update .npmrc to remove repo-level Gitea auth
- Create Primitives.tsx product adapter with ChronoMind-specific status types
- Add ui directory for shared component imports

Generated with [Devin](https://cli.devin.ai/docs)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-05-09 22:10:55 +00:00

815 lines
19 KiB
Markdown

# UX Implementation Guide
> **Purpose:** Comprehensive guide for UX implementation across ByteLyst products using common platform UI packages, design tokens, and Cipher design system integration.
> **Target Audience:** Product teams implementing UX improvements and testing infrastructure.
> **Product:** ChronoMind - AI-powered contextual clock & timer
---
## Overview
This guide documents the complete UX implementation approach for ChronoMind, replicating the patterns established in the trading dashboard. The approach covers:
- **Common Platform UI Integration** - Leveraging `@bytelyst/ui` shared primitives
- **Design Token Usage** - Using `@bytelyst/design-tokens` for consistent styling
- **Component Normalization** - Replacing one-off components with shared primitives
- **Accessibility Improvements** - Keyboard navigation, ARIA labels, focus management
- **Responsive Design** - Viewport matrix testing, shell breakpoints
- **Testing Infrastructure** - Playwright E2E tests, Storybook, AI-friendly reports
- **Cipher Design System** - Visual hierarchy, spacing, typography principles
---
## Part 1: Common Platform UI Integration
### Local Package Resolution
**Critical:** Use local common platform packages by default, not Gitea registry.
**File:** `.pnpmfile.cjs`
```javascript
// .pnpmfile.cjs
// Default to local common platform packages
function readPackage(pkg, context) {
if (!context.workspace) return pkg;
// Default to common-plat (local packages)
const packageSource = process.env.BYTELYST_PACKAGE_SOURCE || 'common-plat';
if (packageSource === 'common-plat') {
// Resolve @bytelyst/* packages from local common platform
if (pkg.name.startsWith('@bytelyst/')) {
pkg.dependencies = pkg.dependencies || {};
pkg.dependencies[pkg.name] = 'workspace:*';
}
}
return pkg;
}
module.exports = {
name: 'bytelyst-package-source',
hooks: {
readPackage,
},
};
```
**File:** `.npmrc`
```ini
# Remove repo-level GITEA_NPM_TOKEN interpolation
# Gitea registry auth should live in user-level ~/.npmrc or CI secrets
# Only when BYTELYST_PACKAGE_SOURCE=gitea is explicitly used
```
### Environment Variables
```bash
# Default: Use local common platform packages
BYTELYST_PACKAGE_SOURCE=common-plat
# Optional: Use Gitea registry (requires auth)
BYTELYST_PACKAGE_SOURCE=gitea
# GITEA_NPM_TOKEN should be in ~/.npmrc or CI secrets
```
### Verification Commands
```bash
# Verify local package resolution
pnpm install @bytelyst/ui @bytelyst/design-tokens
# Verify packages resolve from local common platform
pnpm list @bytelyst/ui
# Should show: @bytelyst/ui -> link:../learning_ai_common_plat/packages/ui
```
### Product Adapter Pattern
Create a product adapter to normalize imports and extend shared primitives with product-specific variants:
**File:** `web/src/components/ui/Primitives.tsx`
```typescript
import * as React from 'react';
import {
Badge as CommonBadge,
Button as CommonButton,
Input as CommonInput,
Select as CommonSelect,
type BadgeProps as CommonBadgeProps,
type ButtonProps as CommonButtonProps,
type InputProps as CommonInputProps,
type SelectProps as CommonSelectProps,
} from '@bytelyst/ui';
// Re-export all shared primitives
export {
ActionMenu,
AlertBanner,
DataList,
DataTable,
Drawer,
EmptyState,
Modal,
PageHeader,
Panel,
Skeleton,
Timeline,
Toolbar,
// ... all other @bytelyst/ui components
} from '@bytelyst/ui';
// Define product-specific variants for ChronoMind
type ProductButtonVariant = NonNullable<CommonButtonProps['variant']> | 'link';
type ProductButtonSize = NonNullable<CommonButtonProps['size']> | 'icon';
type ProductFieldVariant = 'surface' | 'muted';
type ProductFieldSize = 'sm' | 'md';
type ProductBadgeVariant = NonNullable<CommonBadgeProps['variant']> | 'danger';
type ProductStatusTone = 'success' | 'warning' | 'error' | 'info' | 'neutral';
// Extend interfaces with product-specific props
export interface ButtonProps extends Omit<CommonButtonProps, 'variant' | 'size'> {
variant?: ProductButtonVariant;
size?: ProductButtonSize;
}
export interface IconButtonProps extends Omit<ButtonProps, 'children'> {
icon: React.ReactNode;
label: string;
}
export interface InputProps extends CommonInputProps {
controlSize?: ProductFieldSize;
variant?: ProductFieldVariant;
}
// ChronoMind-specific status mapping for badges
export type ChronoMindStatus =
| 'running' | 'paused' | 'stopped' | 'completed' | 'alarm'
| 'snoozed' | 'overdue' | 'upcoming' | 'recurring' | 'one-time'
| 'focus' | 'break' | 'pomodoro' | 'routine' | 'error' | 'success';
const chronomindStatusTone: Record<ChronoMindStatus, ProductStatusTone> = {
running: 'success',
paused: 'warning',
stopped: 'neutral',
completed: 'success',
alarm: 'error',
snoozed: 'warning',
overdue: 'error',
upcoming: 'info',
recurring: 'info',
'one-time': 'neutral',
focus: 'success',
break: 'info',
pomodoro: 'success',
routine: 'info',
error: 'error',
success: 'success',
};
// Helper function to map timer status to tone
export function statusToneFor(status: ChronoMindStatus | string | null | undefined): ProductStatusTone {
if (!status) return 'neutral';
const normalized = status.trim().toLowerCase().replace(/[\s_]+/g, '-') as ChronoMindStatus;
return chronomindStatusTone[normalized] ?? 'neutral';
}
// Product-specific component implementations
export const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
({ variant = 'primary', size = 'md', className, ...props }, ref) => (
<CommonButton
ref={ref}
variant={variant === 'link' ? 'ghost' : variant}
size={size === 'icon' ? 'sm' : size}
className={className}
{...props}
/>
),
);
export const IconButton = React.forwardRef<HTMLButtonElement, IconButtonProps>(
({ icon, label, variant = 'ghost', size = 'icon', className, ...props }, ref) => (
<Button
ref={ref}
type="button"
aria-label={label}
variant={variant}
size={size}
className="shrink-0"
{...props}
>
{icon}
</Button>
),
);
export const Input = React.forwardRef<HTMLInputElement, InputProps>(
({ controlSize = 'md', variant = 'surface', className, ...props }, ref) => (
<CommonInput
ref={ref}
className={className}
{...props}
/>
),
);
export function ChronoMindStatusBadge({
status,
children,
}: {
status: ChronoMindStatus | string | null | undefined;
children?: React.ReactNode;
}) {
return (
<Badge variant={statusToneFor(status)} dot>
{children ?? status ?? 'Unknown'}
</Badge>
);
}
```
**Benefits of Product Adapter Pattern:**
- Centralized import point for all UI components
- Product-specific variants without modifying common platform
- Consistent styling across the application
- Easy to migrate to new common platform versions
- Type-safe extensions with TypeScript
---
## Part 2: Design Token Usage
### CSS Variable Integration
Use design tokens from `@bytelyst/design-tokens` via CSS custom properties with ChronoMind-specific prefixes:
```css
/* Surface colors */
background: var(--cm-surface-card);
background: var(--cm-surface-muted);
background: var(--cm-surface-overlay);
/* Text colors */
color: var(--cm-text-primary);
color: var(--cm-text-secondary);
color: var(--cm-text-muted);
/* Border colors */
border-color: var(--cm-border);
border-color: var(--cm-border-muted);
/* Input styling */
background: var(--cm-input);
color: var(--cm-text-primary);
border-color: var(--cm-border);
/* Focus states */
border-color: var(--cm-focus-ring);
box-shadow: 0 0 0 2px var(--cm-focus-ring-muted);
/* Semantic colors */
color: var(--cm-accent);
background: var(--cm-success);
background: var(--cm-warning);
background: var(--cm-error);
/* Spacing */
padding: var(--cm-spacing-sm);
padding: var(--cm-spacing-md);
padding: var(--cm-spacing-lg);
padding: var(--cm-spacing-xl);
/* Border radius */
border-radius: var(--cm-radius-control);
border-radius: var(--cm-radius-md);
border-radius: var(--cm-radius-lg);
/* Typography */
font-size: var(--cm-text-sm);
font-size: var(--cm-text-base);
font-size: var(--cm-text-lg);
font-weight: var(--cm-font-medium);
font-weight: var(--cm-font-semibold);
```
### Component Token Patterns
**Example: Badge Component for ChronoMind**
```typescript
// Using design tokens for badge styling
const badgeStyles = {
success: {
background: 'var(--cm-success-light)',
color: 'var(--cm-success-dark)',
borderColor: 'var(--cm-success)',
},
warning: {
background: 'var(--cm-warning-light)',
color: 'var(--cm-warning-dark)',
borderColor: 'var(--cm-warning)',
},
error: {
background: 'var(--cm-error-light)',
color: 'var(--cm-error-dark)',
borderColor: 'var(--cm-error)',
},
};
```
---
## Part 3: Component Normalization
### Badge Normalization
**Before:** One-off CSS classes for different badge styles
```css
/* Old approach - one-off classes */
.timer-chip {
background: #f0f0f0;
padding: 4px 8px;
border-radius: 4px;
font-size: 12px;
}
.category-tag {
background: #e0e0e0;
padding: 6px 12px;
border-radius: 6px;
}
.status-pill {
background: #d0d0d0;
padding: 5px 10px;
border-radius: 20px;
}
```
**After:** Shared Badge component from product adapter
```typescript
import { Badge, ChronoMindStatusBadge } from '../components/ui/Primitives';
// Replace .timer-chip with Badge
<Badge variant="success">Running</Badge>
// Replace .category-tag with Badge
<Badge variant="info">Work</Badge>
// Replace .status-pill with ChronoMindStatusBadge
<ChronoMindStatusBadge status="pomodoro" />
```
### Alert Banner Unification
**Before:** Different alert implementations across components
```typescript
// TimerCard.tsx - custom alert
<div className="alert-banner" style={{ background: '#fff3cd' }}>
<span>⚠️</span>
<span>Warning message</span>
</div>
// RoutineEditor.tsx - different alert
<div className="warning-box" style={{ border: '1px solid #ffc107' }}>
<div className="warning-icon">!</div>
<div className="warning-text">Warning text</div>
</div>
```
**After:** Shared AlertBanner component
```typescript
import { AlertBanner } from '../components/ui/Primitives';
// Both components now use shared AlertBanner
<AlertBanner tone="warning" title="Warning">
Warning message
</AlertBanner>
<AlertBanner tone="error" title="Error">
Error message
</AlertBanner>
```
### Table Controls Standardization
**Before:** Custom table controls with inconsistent styling
```typescript
// Different button styles across tables
<button className="table-action-btn-small">Edit</button>
<button className="timer-filter-btn">Filter</button>
<button className="routine-action">Complete</button>
```
**After:** Standardized Button component
```typescript
import { Button, IconButton } from '../components/ui/Primitives';
// Consistent button styles
<Button variant="ghost" size="sm">Edit</Button>
<Button variant="secondary" size="sm">Filter</Button>
<IconButton icon={<CheckIcon />} label="Complete" />
```
---
## Part 4: Accessibility Improvements
### Keyboard Navigation
**Focus Management**
```typescript
// Ensure interactive elements are focusable
<button
type="button"
tabIndex={0}
onKeyDown={(e) => {
if (e.key === 'Enter' || e.key === ' ') {
e.preventDefault();
handleClick();
}
}}
>
Action
</button>
```
**Keyboard Toggles**
```typescript
<div
role="button"
tabIndex={0}
onKeyDown={(e) => {
if (e.key === 'Enter' || e.key === ' ') {
e.preventDefault();
toggle();
}
}}
aria-pressed={isActive}
>
Toggle Option
</div>
```
### ARIA Labels
**Button Labels**
```typescript
// Icon buttons need explicit labels
<IconButton
icon={<PlayIcon />}
label="Start timer"
aria-label="Start timer"
/>
```
**Status Indicators**
```typescript
// Status badges need descriptive labels
<ChronoMindStatusBadge
status="running"
aria-label="Status: Running"
>
Running
</ChronoMindStatusBadge>
```
**Form Labels**
```typescript
// All inputs need associated labels
<label htmlFor="timer-name">
Timer Name
<Input id="timer-name" type="text" />
</label>
```
### Focus Indicators
**Visible Focus States**
```css
/* Ensure focus is visible */
*:focus-visible {
outline: 2px solid var(--cm-focus-ring);
outline-offset: 2px;
}
/* Or use design token */
*:focus-visible {
outline: 2px solid var(--cm-focus-ring);
box-shadow: 0 0 0 2px var(--cm-focus-ring-muted);
}
```
---
## Part 5: Responsive Design
### Shell Breakpoints
**Responsive Shell Testing**
```typescript
const breakpoints = {
mobile: 'max-width: 560px',
tablet: 'max-width: 768px',
desktop: 'min-width: 769px',
};
// Shell adapts at these breakpoints
@media (max-width: 560px) {
.timer-main {
margin-left: 0;
}
.timer-right-panel {
display: none;
}
.timer-sidebar {
position: fixed;
bottom: 0;
width: 100%;
height: 60px;
}
}
```
### Viewport Matrix Testing
**Test all routes across viewports**
```typescript
const routes = ['/dashboard', '/focus', '/history', '/routines', '/settings'];
const viewports = [
{ name: 'Desktop', width: 1200, height: 800 },
{ name: 'Tablet', width: 768, height: 1024 },
{ name: 'Mobile', width: 375, height: 667 },
];
routes.forEach((route) => {
viewports.forEach((viewport) => {
test(`${route} - ${viewport.name} viewport`, async ({ page }) => {
await page.setViewportSize({ width: viewport.width, height: viewport.height });
await page.goto(route);
// Check for horizontal overflow
const bodyWidth = await page.evaluate(() => document.body.scrollWidth);
expect(bodyWidth).toBeLessThanOrEqual(viewport.width + 10);
// Check main content visibility
const mainContent = page.locator('main');
await expect(mainContent).toBeVisible();
});
});
});
```
---
## Part 6: Testing Infrastructure
### Playwright Setup
**Install Dependencies**
```bash
cd web
pnpm add -D @playwright/test
pnpm exec playwright install chromium
```
**Create Playwright Config**
Create `web/playwright.config.ts`:
```typescript
import { defineConfig, devices } from '@playwright/test';
export default defineConfig({
testDir: './e2e',
fullyParallel: true,
forbidOnly: !!process.env.CI,
retries: process.env.CI ? 2 : 0,
workers: process.env.CI ? 1 : undefined,
reporter: 'html',
use: {
baseURL: 'http://localhost:3030',
trace: 'on-first-retry',
screenshot: 'only-on-failure',
},
projects: [
{ name: 'chromium', use: { ...devices['Desktop Chrome'] } },
{ name: 'firefox', use: { ...devices['Desktop Firefox'] } },
{ name: 'webkit', use: { ...devices['Desktop Safari'] } },
{ name: 'Mobile Chrome', use: { ...devices['Pixel 5'] } },
{ name: 'Mobile Safari', use: { ...devices['iPhone 12'] } },
],
});
```
### Test Suite Structure
Create tests in `web/e2e/` directory:
```
web/e2e/
├── viewport-matrix.spec.ts # Viewport compliance
├── horizontal-overflow.spec.ts # Overflow detection
├── alert-positioning.spec.ts # Critical alerts positioning
├── timer-flows.spec.ts # Timer start/pause/stop flows
├── page-states.spec.ts # Loading/empty/error/success states
├── form-validation.spec.ts # Form validation
└── keyboard-navigation.spec.ts # Keyboard navigation
```
---
## Part 7: Cipher Design System Integration
### Design Principles
Follow Cipher design system principles for consistent UX across ChronoMind:
#### 1. Visual Hierarchy
**Size and Weight**
```typescript
// Use design tokens for consistent hierarchy
<h1 style={{ fontSize: 'var(--cm-text-2xl)', fontWeight: 'var(--cm-font-bold)' }}>
Timer Title
</h1>
<h2 style={{ fontSize: 'var(--cm-text-xl)', fontWeight: 'var(--cm-font-semibold)' }}>
Section Header
</h2>
<p style={{ fontSize: 'var(--cm-text-base)', color: 'var(--cm-text-secondary)' }}>
Timer content
</p>
```
#### 2. Spacing System
**Consistent Spacing**
```typescript
// Use design token spacing
<div style={{ padding: 'var(--cm-spacing-md)' }}>
Content
</div>
<div style={{ gap: 'var(--cm-spacing-sm)' }}>
<Button>First</Button>
<Button>Second</Button>
</div>
```
#### 3. Typography
**Font Scales**
```typescript
// Use design token font sizes
const textStyles = {
xs: 'var(--cm-text-xs)',
sm: 'var(--cm-text-sm)',
base: 'var(--cm-text-base)',
lg: 'var(--cm-text-lg)',
xl: 'var(--cm-text-xl)',
'2xl': 'var(--cm-text-2xl)',
};
<p style={{ fontSize: textStyles.base }}>Body text</p>
```
#### 4. Color System
**Semantic Colors**
```typescript
// Use semantic color tokens, not literal colors
const statusColors = {
success: 'var(--cm-success)',
warning: 'var(--cm-warning)',
error: 'var(--cm-error)',
info: 'var(--cm-info)',
};
<div style={{ color: statusColors.success }}>Success message</div>
```
---
## Part 8: Implementation Roadmap
### Phase 1: Foundation Setup
**Week 1-2**
- [ ] Set up local package resolution (.pnpmfile.cjs)
- [ ] Remove repo-level GITEA_NPM_TOKEN from .npmrc
- [ ] Install common platform packages from local source
- [ ] Create product adapter (`Primitives.tsx`)
- [ ] Set up design token integration
- [ ] Configure Playwright
### Phase 2: Component Normalization
**Week 3-4**
- [ ] Replace one-off badges with Badge component
- [ ] Replace custom buttons with Button component
- [ ] Replace custom alerts with AlertBanner component
- [ ] Replace custom inputs with Input component
- [ ] Remove old CSS classes
### Phase 3: Design Token Migration
**Week 5-6**
- [ ] Identify hardcoded colors
- [ ] Map to semantic tokens
- [ ] Replace with CSS variables
- [ ] Test across themes
- [ ] Audit for missed tokens
### Phase 4: Accessibility Improvements
**Week 7**
- [ ] Add keyboard navigation
- [ ] Add ARIA labels
- [ ] Improve focus indicators
- [ ] Test with screen readers
### Phase 5: Responsive Design
**Week 8**
- [ ] Test viewport matrix
- [ ] Fix horizontal overflow
- [ ] Optimize mobile layout
- [ ] Test breakpoints
### Phase 6: Testing Infrastructure
**Week 9-10**
- [ ] Create E2E test suite
- [ ] Set up test runner script
- [ ] Configure AI-friendly reports
- [ ] Integrate with CI
---
## Part 9: Verification Commands
### Type Checking
```bash
# Verify TypeScript types
pnpm run typecheck
```
### Build Verification
```bash
# Ensure build succeeds
pnpm run build
```
### Test Execution
```bash
# Run all E2E tests
cd web
pnpm test:e2e
# Run specific test suites
pnpm test:e2e:viewport
pnpm test:e2e:overflow
pnpm test:e2e:timer-flows
```
---
## Summary
This guide provides a comprehensive approach to implementing UX improvements for ChronoMind using the ByteLyst common platform UI packages, design tokens, and Cipher design system. By following these patterns, ChronoMind will achieve:
- Consistent UI with other ByteLyst products
- Improved accessibility and keyboard navigation
- Responsive design across all viewports
- Comprehensive testing infrastructure
- Maintainable and scalable codebase
The implementation is organized in phases to allow for incremental adoption and verification at each stage.