learning_ai_invt_trdg/docs/UX_TESTING_SETUP_GUIDE.md
Saravana Achu Mac e87d7a5839 docs: add local package resolution section to UX guide
Added critical section on using local common platform packages instead of Gitea registry:

- .pnpmfile.cjs configuration for local package resolution
- .npmrc configuration (remove repo-level GITEA_NPM_TOKEN)
- Environment variables (BYTELYST_PACKAGE_SOURCE)
- Verification commands to confirm local resolution
- Benefits of local package resolution (faster dev, offline, no registry dependency)
- Updated Phase 1 roadmap to include local package setup

This ensures other products follow the same approach of using local common-plat packages by default instead of pulling from Gitea registry.
2026-05-09 14:21:46 -07:00

33 KiB

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.


Overview

This guide documents the complete UX implementation approach for the trading dashboard, which can be replicated across all ByteLyst products. 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

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

# 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

Commit Reference: f2216ad - chore(pnpm): prefer local bytelyst packages

Environment Variables

# 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

# 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

Benefits of Local Package Resolution

  1. Faster Development - No network calls to registry during development
  2. Immediate Updates - Changes to common platform are immediately available
  3. No Registry Dependency - Works offline and without Gitea access
  4. Consistent Builds - Same packages across all environments
  5. Simpler CI - No need for registry auth in CI by default

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

import * as React from 'react';
import {
  Badge as CommonBadge,
  Button as CommonButton,
  Field,
  FieldContent,
  FieldDescription,
  FieldError,
  FieldGroup,
  FieldLabel,
  FieldTitle,
  Input as CommonInput,
  Select as CommonSelect,
  Textarea as CommonTextarea,
  type BadgeProps as CommonBadgeProps,
  type ButtonProps as CommonButtonProps,
  type InputProps as CommonInputProps,
  type SelectProps as CommonSelectProps,
  type TextareaProps as CommonTextareaProps,
} from '@bytelyst/ui';

// Re-export all shared primitives
export {
  ActionMenu,
  AlertBanner,
  DataList,
  DataTable,
  Drawer,
  EmptyState,
  EntityCard,
  FieldGrid,
  FilterBar,
  FormSection,
  MetricCard,
  Modal,
  PageHeader,
  Panel,
  Skeleton,
  Timeline,
  Toolbar,
  // ... all other @bytelyst/ui components
} from '@bytelyst/ui';

// Define product-specific variants
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;
}

// Product status mapping for badges
export type ProductStatus =
  | 'active' | 'approved' | 'blocked' | 'buy' | 'cancelled'
  | 'connected' | 'danger' | 'degraded' | 'disabled' | 'error'
  | 'failed' | 'idle' | 'info' | 'live' | 'neutral' | 'off'
  | 'ok' | 'paper' | 'pending' | 'rejected' | 'sell' | 'success'
  | 'synced' | 'warning';

const productStatusTone: Record<ProductStatus, ProductStatusTone> = {
  active: 'success',
  approved: 'success',
  blocked: 'error',
  buy: 'success',
  cancelled: 'neutral',
  connected: 'success',
  danger: 'error',
  degraded: 'warning',
  disabled: 'neutral',
  error: 'error',
  failed: 'error',
  idle: 'neutral',
  info: 'info',
  live: 'warning',
  neutral: 'neutral',
  off: 'neutral',
  ok: 'success',
  paper: 'info',
  pending: 'warning',
  rejected: 'error',
  sell: 'error',
  success: 'success',
  synced: 'success',
  warning: 'warning',
};

// Helper function to map product status to tone
export function statusToneFor(status: ProductStatus | string | null | undefined): ProductStatusTone {
  if (!status) return 'neutral';
  const normalized = status.trim().toLowerCase().replace(/[\s_]+/g, '-') as ProductStatus;
  return productStatusTone[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={cn('product-button', 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={cn('shrink-0', className)}
      {...props}
    >
      {icon}
    </Button>
  ),
);

export const Input = React.forwardRef<HTMLInputElement, InputProps>(
  ({ controlSize = 'md', variant = 'surface', className, ...props }, ref) => (
    <CommonInput
      ref={ref}
      className={cn(
        controlSize === 'sm' ? 'min-h-9 px-3 py-2 text-xs' : 'min-h-11 px-3.5 py-2.5 text-sm',
        variant === 'surface' ? 'bg-[var(--bl-input)]' : 'bg-[var(--bl-surface-muted)]',
        className,
      )}
      {...props}
    />
  ),
);

export function ProductStatusBadge({
  status,
  children,
}: {
  status: ProductStatus | 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:

/* Surface colors */
background: var(--bl-surface-card);
background: var(--bl-surface-muted);
background: var(--bl-surface-overlay);

/* Text colors */
color: var(--bl-text-primary);
color: var(--bl-text-secondary);
color: var(--bl-text-muted);

/* Border colors */
border-color: var(--bl-border);
border-color: var(--bl-border-muted);

/* Input styling */
background: var(--bl-input);
color: var(--bl-text-primary);
border-color: var(--bl-border);

/* Focus states */
border-color: var(--bl-focus-ring);
box-shadow: 0 0 0 2px var(--bl-focus-ring-muted);

/* Semantic colors */
color: var(--bl-accent);
background: var(--bl-success);
background: var(--bl-warning);
background: var(--bl-error);

/* Spacing */
padding: var(--bl-spacing-sm);
padding: var(--bl-spacing-md);
padding: var(--bl-spacing-lg);
padding: var(--bl-spacing-xl);

/* Border radius */
border-radius: var(--bl-radius-control);
border-radius: var(--bl-radius-md);
border-radius: var(--bl-radius-lg);

/* Typography */
font-size: var(--bl-text-sm);
font-size: var(--bl-text-base);
font-size: var(--bl-text-lg);
font-weight: var(--bl-font-medium);
font-weight: var(--bl-font-semibold);

Component Token Patterns

Example: Badge Component

// Using design tokens for badge styling
const badgeStyles = {
  success: {
    background: 'var(--bl-success-light)',
    color: 'var(--bl-success-dark)',
    borderColor: 'var(--bl-success)',
  },
  warning: {
    background: 'var(--bl-warning-light)',
    color: 'var(--bl-warning-dark)',
    borderColor: 'var(--bl-warning)',
  },
  error: {
    background: 'var(--bl-error-light)',
    color: 'var(--bl-error-dark)',
    borderColor: 'var(--bl-error)',
  },
};

Token Migration Checklist

When migrating to design tokens:

  1. Identify hardcoded colors - Search for hex codes and named colors
  2. Map to semantic tokens - Use semantic names (success, warning, error) not literal colors
  3. Replace with CSS variables - Use var(--bl-*) syntax
  4. Test across themes - Ensure tokens work in light/dark modes
  5. Audit for missed tokens - Use audit:ui to find remaining hardcoded values

Part 3: Component Normalization

Badge Normalization

Before: One-off CSS classes for different badge styles

/* Old approach - one-off classes */
.stat-chip {
  background: #f0f0f0;
  padding: 4px 8px;
  border-radius: 4px;
  font-size: 12px;
}

.saved-setup-id-chip {
  background: #e0e0e0;
  padding: 6px 12px;
  border-radius: 6px;
}

.health-pill {
  background: #d0d0d0;
  padding: 5px 10px;
  border-radius: 20px;
}

After: Shared Badge component from product adapter

import { Badge, ProductStatusBadge } from '../components/ui/Primitives';

// Replace .stat-chip with Badge
<Badge variant="success">Active</Badge>

// Replace .saved-setup-id-chip with Badge
<Badge variant="info">Setup #123</Badge>

// Replace .health-pill with ProductStatusBadge
<ProductStatusBadge status="healthy" />

Commit: 94ce743 - refactor(ui): replace one-off visual language with shared Badge components

Alert Banner Unification

Before: Different alert implementations across components

// HistoryTab.tsx - custom alert
<div className="alert-banner" style={{ background: '#fff3cd' }}>
  <span>⚠️</span>
  <span>Warning message</span>
</div>

// PositionsTab.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

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>

Commit: d4846b7 - refactor(ui): unify operational alert banners

Table Controls Standardization

Before: Custom table controls with inconsistent styling

// Different button styles across tables
<button className="table-action-btn-small">Edit</button>
<button className="history-filter-btn">Filter</button>
<button className="backtest-action">Run</button>

After: Standardized Button component

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={<PlayIcon />} label="Run" />

Commits:

  • 3951767 - refactor(ui): standardize operations table badges
  • fd64fec - refactor(ui): refine positions table controls
  • bfd7d3b - refactor(ui): align history filters controls

Control Normalization Pattern

Step 1: Identify one-off controls

# Search for custom CSS classes
grep -r "className.*chip" web/src/
grep -r "className.*btn" web/src/
grep -r "className.*pill" web/src/

Step 2: Map to shared primitives

  • Chips → Badge
  • Custom buttons → Button
  • Custom inputs → Input
  • Custom selects → Select
  • Custom alerts → AlertBanner

Step 3: Replace component by component

// Before
<div className="custom-chip">Text</div>

// After
<Badge variant="neutral">Text</Badge>

Step 4: Remove old CSS

/* Delete one-off CSS classes */
.custom-chip { /* delete */ }
.custom-btn { /* delete */ }

Step 5: Verify with audit

pnpm run audit:ui

Part 4: Accessibility Improvements

Keyboard Navigation

Focus Management

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

// Commit: a65d726 - test(ui): cover profile rule keyboard toggles
<div
  role="button"
  tabIndex={0}
  onKeyDown={(e) => {
    if (e.key === 'Enter' || e.key === ' ') {
      e.preventDefault();
      toggle();
    }
  }}
  aria-pressed={isActive}
>
  Toggle Option
</div>

Commit: 3c52593 - fix(ui): improve profile rule keyboard access

ARIA Labels

Button Labels

// Icon buttons need explicit labels
<IconButton
  icon={<EditIcon />}
  label="Edit item"
  aria-label="Edit item"
/>

Status Indicators

// Status badges need descriptive labels
<ProductStatusBadge
  status="active"
  aria-label="Status: Active"
>
  Active
</ProductStatusBadge>

Form Labels

// All inputs need associated labels
<Field>
  <FieldLabel htmlFor="email">Email</FieldLabel>
  <Input id="email" type="email" />
  <FieldDescription>Enter your email address</FieldDescription>
</Field>

Focus Indicators

Visible Focus States

/* Ensure focus is visible */
*:focus-visible {
  outline: 2px solid var(--bl-focus-ring);
  outline-offset: 2px;
}

/* Or use design token */
*:focus-visible {
  outline: 2px solid var(--bl-focus-ring);
  box-shadow: 0 0 0 2px var(--bl-focus-ring-muted);
}

Focus Order

// Ensure logical tab order
<div className="flex gap-4">
  <Button>First</Button>
  <Button>Second</Button>
  <Button>Third</Button>
</div>

// Use tabIndex to control order if needed
<Button tabIndex={1}>First</Button>
<Button tabIndex={2}>Second</Button>

Part 5: Responsive Design

Shell Breakpoints

Responsive Shell Testing

// Commit: c51544a - test(ui): lock responsive shell breakpoints
const breakpoints = {
  mobile: 'max-width: 560px',
  tablet: 'max-width: 768px',
  desktop: 'min-width: 769px',
};

// Shell adapts at these breakpoints
@media (max-width: 560px) {
  .dashboard-main {
    margin-left: 0;
  }
  .dashboard-right-panel {
    display: none;
  }
  .trading-sidebar {
    position: fixed;
    bottom: 0;
    width: 100%;
    height: 60px;
  }
}

Viewport Matrix Testing

Test all routes across viewports

// Commit: 79f0021 - test(ui): add comprehensive Playwright E2E test suite
const routes = ['/', '/portfolio', '/research', '/plans', '/markets'];
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();
    });
  });
});

Mobile-First Patterns

Responsive Components

// Use responsive design tokens
<div
  style={{
    padding: 'var(--bl-spacing-md)',
    '@media (max-width: 560px)': {
      padding: 'var(--bl-spacing-sm)',
    },
  }}
>
  Content
</div>

Conditional Rendering

// Show/hide based on viewport
const isMobile = useMediaQuery('(max-width: 560px)');

{isMobile ? (
  <MobileLayout />
) : (
  <DesktopLayout />
)}

Part 6: Testing Infrastructure

Playwright Setup

Install Dependencies

cd web
pnpm add -D @playwright/test
pnpm exec playwright install chromium

Create Playwright Config

Create web/playwright.config.ts:

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:3050',
    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'] } },
    { name: 'Desktop Chrome HiDPI', use: { ...devices['Desktop Chrome HiDPI'] } },
  ],
});

Commit: 8db23bd - test(ui): add Playwright and Storybook testing infrastructure

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
├── assistant-positioning.spec.ts # Assistant widget positioning
├── destructive-actions.spec.ts  # Destructive actions confirmation
├── feedback.spec.ts             # Save/delete/update feedback
├── page-states.spec.ts          # Loading/empty/error/success states
├── form-validation.spec.ts      # Form validation
└── keyboard-navigation.spec.ts   # Keyboard navigation

Commit: 79f0021 - test(ui): add comprehensive Playwright E2E test suite

Test Runner Script

Create scripts/tests/run-e2e.sh with server lifecycle management:

#!/bin/bash
set -e

# Colors for visual output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
PURPLE='\033[0;35m'
CYAN='\033[0;36m'
BOLD='\033[1m'
NC='\033[0m'

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
WEB_DIR="$PROJECT_ROOT/web"
REPORTS_DIR="$SCRIPT_DIR/reports"
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
PORT=3050

cd "$WEB_DIR"

# Function to kill any process on the specified port
kill_port() {
    local port=$1
    local pid=$(lsof -ti:$port 2>/dev/null || echo "")
    if [ -n "$pid" ]; then
        kill -9 $pid 2>/dev/null || true
        sleep 2
    fi
}

# Function to wait for server to be ready
wait_for_server() {
    local max_attempts=30
    local attempt=0
    while [ $attempt -lt $max_attempts ]; do
        if curl -s http://localhost:$PORT > /dev/null 2>&1; then
            return 0
        fi
        attempt=$((attempt + 1))
        sleep 2
    done
    return 1
}

# Kill existing server, start fresh, run tests, cleanup
kill_port $PORT
pnpm dev --port $PORT --strict-port > "$REPORTS_DIR/server-$TIMESTAMP.log" 2>&1 &
SERVER_PID=$!

if ! wait_for_server; then
    kill_port $PORT
    exit 1
fi

pnpm exec playwright test \
    --reporter=json \
    --reporter=list \
    --output="$REPORTS_DIR/results-$TIMESTAMP.json" \
    2>&1 | tee "$REPORTS_DIR/test-output-$TIMESTAMP.log"
TEST_EXIT_CODE=$?

kill $SERVER_PID 2>/dev/null || true
kill_port $PORT

exit $TEST_EXIT_CODE

Commits:

  • a572293 - test(ui): enhance E2E test runner with AI-friendly detailed reports
  • df7c57e - feat(test): add automatic server lifecycle management to run-e2e.sh
  • 2bd38e1 - ui(test): enhance script output with visual formatting and colors

AI-Friendly Report Generation

The test runner generates three types of reports:

  1. JSON Report - Full machine-readable data
  2. Markdown Report - Human-readable summary
  3. AI Summary - Optimized for AI agents with actionable next steps

AI Summary Format:

{
  "timestamp": "20260509_135006",
  "test_status": "passed",
  "test_exit_code": 0,
  "tests_total": 333,
  "tests_passed": 333,
  "tests_failed": 0,
  "duration_seconds": 447,
  "health_check": {
    "status": "healthy",
    "app_running": true,
    "app_url": "http://localhost:3050"
  },
  "for_ai_agents": {
    "quick_summary": "passed - 333/333 tests passed in 447s",
    "if_failed_check": "scripts/tests/reports/test-output-20260509_135006.log",
    "html_report": "web/playwright-report/index.html",
    "next_action": "Review HTML report for performance optimization"
  }
}

Storybook Setup

Install Storybook

cd web
pnpm add -D @storybook/react @storybook/addon-essentials @storybook/addon-interactions

Create Storybook Config

Create web/.storybook/main.ts:

import type { StorybookConfig } from '@storybook/react-vite';

const config: StorybookConfig = {
  stories: ['../src/**/*.mdx', '../src/**/*.stories.@(js|jsx|ts|tsx)'],
  addons: [
    '@storybook/addon-essentials',
    '@storybook/addon-interactions',
  ],
  framework: {
    name: '@storybook/react-vite',
    options: {},
  },
  docs: {
    autodocs: 'tag',
  },
};

export default config;

Commit: 8db23bd - test(ui): add Playwright and Storybook testing infrastructure


Part 7: Cipher Design System Integration

Design Principles

Follow Cipher design system principles for consistent UX across products:

1. Visual Hierarchy

Size and Weight

// Use design tokens for consistent hierarchy
<h1 style={{ fontSize: 'var(--bl-text-2xl)', fontWeight: 'var(--bl-font-bold)' }}>
  Primary Heading
</h1>
<h2 style={{ fontSize: 'var(--bl-text-xl)', fontWeight: 'var(--bl-font-semibold)' }}>
  Secondary Heading
</h2>
<p style={{ fontSize: 'var(--bl-text-base)', color: 'var(--bl-text-secondary)' }}>
  Body text
</p>

2. Spacing System

Consistent Spacing

// Use design token spacing
<div style={{ padding: 'var(--bl-spacing-md)' }}>
  Content
</div>

<div style={{ gap: 'var(--bl-spacing-sm)' }}>
  <Button>First</Button>
  <Button>Second</Button>
</div>

3. Typography

Font Scales

// Use design token font sizes
const textStyles = {
  xs: 'var(--bl-text-xs)',
  sm: 'var(--bl-text-sm)',
  base: 'var(--bl-text-base)',
  lg: 'var(--bl-text-lg)',
  xl: 'var(--bl-text-xl)',
  '2xl': 'var(--bl-text-2xl)',
};

<p style={{ fontSize: textStyles.base }}>Body text</p>

4. Color System

Semantic Colors

// Use semantic color tokens, not literal colors
const statusColors = {
  success: 'var(--bl-success)',
  warning: 'var(--bl-warning)',
  error: 'var(--bl-error)',
  info: 'var(--bl-info)',
};

<div style={{ color: statusColors.success }}>Success message</div>

5. Accessibility

WCAG AA Compliance

// Ensure contrast ratios meet WCAG AA
// Design tokens already ensure compliance
<button style={{
  background: 'var(--bl-accent)',
  color: 'var(--bl-accent-foreground)',
}}>
  Accessible Button
</button>

Component Patterns

Card Pattern

<div style={{
  background: 'var(--bl-surface-card)',
  border: '1px solid var(--bl-border)',
  borderRadius: 'var(--bl-radius-md)',
  padding: 'var(--bl-spacing-lg)',
  boxShadow: 'var(--bl-shadow-sm)',
}}>
  <h3 style={{ fontSize: 'var(--bl-text-lg)', fontWeight: 'var(--bl-font-semibold)' }}>
    Card Title
  </h3>
  <p style={{ color: 'var(--bl-text-secondary)', marginTop: 'var(--bl-spacing-sm)' }}>
    Card content
  </p>
</div>

Button Pattern

<Button variant="primary" size="md">
  Primary Action
</Button>

<Button variant="secondary" size="md">
  Secondary Action
</Button>

<Button variant="ghost" size="md">
  Ghost Action
</Button>

Alert Pattern

<AlertBanner tone="info" title="Information">
  Info message
</AlertBanner>

<AlertBanner tone="warning" title="Warning">
  Warning message
</AlertBanner>

<AlertBanner tone="error" title="Error">
  Error message
</AlertBanner>

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
  • Configure Storybook

Verification:

# Verify local package resolution
pnpm install @bytelyst/ui @bytelyst/design-tokens
pnpm list @bytelyst/ui
# Should show: @bytelyst/ui -> link:../learning_ai_common_plat/packages/ui

# Verify build works with local packages
pnpm run typecheck
pnpm run build

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

Commits Reference:

  • 94ce743 - Badge normalization
  • d4846b7 - Alert banner unification
  • 3951767 - Table badges standardization
  • Multiple control normalization commits

Verification:

pnpm run audit:ui
# Should show 0 raw controls, 0 direct @bytelyst/ui imports

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

Verification:

# Search for hardcoded colors
grep -r "#[0-9a-fA-F]\{6\}" web/src/
# Should return minimal results (semantic colors only)

Phase 4: Accessibility Improvements

Week 7

  • Add keyboard navigation
  • Add ARIA labels
  • Improve focus indicators
  • Test with screen readers

Commits Reference:

  • 3c52593 - Profile rule keyboard access
  • a65d726 - Profile rule keyboard toggles

Verification:

# Run accessibility tests
pnpm exec playwright test e2e/keyboard-navigation.spec.ts
pnpm exec playwright test e2e/form-validation.spec.ts

Phase 5: Responsive Design

Week 8

  • Test viewport matrix
  • Fix horizontal overflow
  • Optimize mobile layout
  • Test breakpoints

Commits Reference:

  • c51544a - Responsive shell breakpoints
  • 31d8932 - Stabilize responsive shell chrome

Verification:

pnpm exec playwright test e2e/viewport-matrix.spec.ts
pnpm exec playwright test e2e/horizontal-overflow.spec.ts

Phase 6: Testing Infrastructure

Week 9-10

  • Create E2E test suite
  • Set up test runner script
  • Configure AI-friendly reports
  • Set up Storybook
  • Integrate with CI

Commits Reference:

  • 8db23bd - Playwright and Storybook setup
  • 79f0021 - Comprehensive E2E test suite
  • a572293 - AI-friendly reports
  • df7c57e - Test runner with server lifecycle

Verification:

./scripts/tests/run-e2e.sh
# Should pass all tests and generate reports

Part 9: Verification Commands

UI Audit

# Check for raw controls and direct imports
pnpm run audit:ui

# Strict audit (enforces token usage)
pnpm run audit:ui:strict

Type Checking

# Verify TypeScript types
pnpm run typecheck

Build Verification

# Ensure build succeeds
pnpm run build

Test Execution

# Run all E2E tests
./scripts/tests/run-e2e.sh

# Run specific test suites
cd web
pnpm test:e2e:viewport
pnpm test:e2e:overflow
pnpm test:e2e:ui

Storybook

# Start Storybook
cd web
pnpm storybook

Part 10: Troubleshooting

Common Issues

Issue: Playwright tests fail on VM

Solution:

cd web
pnpm install

Issue: Design tokens not working

Solution:

# Ensure design tokens package is installed
pnpm install @bytelyst/design-tokens

# Check CSS variable usage
grep -r "var(--bl-)" web/src/

Issue: Component imports failing

Solution:

# Ensure common platform is accessible
cd ../learning_ai_common_plat
pnpm build

# In product repo
pnpm install

Issue: Audit showing raw controls

Solution:

# Find raw controls
grep -r "className.*btn" web/src/
grep -r "className.*chip" web/src/

# Replace with shared primitives

Part 11: Best Practices

Component Development

  1. Use shared primitives first - Always check @bytelyst/ui before creating custom components
  2. Follow product adapter pattern - Extend shared primitives via product adapter, not direct modification
  3. Use design tokens - Never hardcode colors, spacing, or typography
  4. Test accessibility - Include keyboard navigation and ARIA labels
  5. Test responsive - Verify components work across viewports

Code Organization

  1. Centralize imports - Use Primitives.tsx for all UI component imports
  2. Group related components - Organize by feature, not by component type
  3. Separate concerns - Keep presentation logic separate from business logic
  4. Use TypeScript - Leverage type safety for component props

Testing Strategy

  1. Test critical paths - Focus on user workflows
  2. Test across browsers - Use Playwright's multi-browser support
  3. Test accessibility - Include keyboard and screen reader tests
  4. Test responsive - Verify viewport matrix compliance
  5. Use AI-friendly reports - Enable automated analysis

Documentation

  1. Document decisions - Record why certain patterns were chosen
  2. Update roadmaps - Keep launch readiness checklist current
  3. Share learnings - Document patterns for other products
  4. Maintain guides - Update this guide as patterns evolve

Part 12: References

Common Platform

  • UI Package: @bytelyst/ui - Shared UI components
  • Design Tokens: @bytelyst/design-tokens - Design system tokens
  • Documentation: Common platform documentation in learning_ai_common_plat

Cipher Design System

  • Principles: Visual hierarchy, spacing, typography, color, accessibility
  • Documentation: Internal design system documentation
  • Reference: https://www.usecipher.dev/

Testing

Project Documentation

  • Launch Readiness Roadmap: docs/inprogress/LAUNCH_READY_UI_UX_ROADMAP.md
  • Production Grade UX: docs/completed/PRODUCTION_GRADE_UX_REMEDIATION_ROADMAP.md
  • UI Migration Roadmap: docs/completed/UI_UX_PLATFORM_CORE_MIGRATION_ROADMAP.md

Summary

This comprehensive guide documents the complete UX implementation approach for the trading dashboard, covering:

Key Achievements (Last 2 Days):

  • 40+ commits focused on UX improvements
  • Complete component normalization using shared primitives
  • Comprehensive design token integration
  • Accessibility improvements across the application
  • Responsive design testing and fixes
  • Full E2E testing infrastructure with Playwright
  • Storybook setup for component development
  • AI-friendly reporting for automated analysis

Reproducible Patterns:

  1. Product Adapter Pattern - Centralized import point with type-safe extensions
  2. Component Normalization - Replace one-off components with shared primitives
  3. Design Token Integration - Use CSS variables for consistent styling
  4. Accessibility First - Keyboard navigation, ARIA labels, focus management
  5. Responsive by Default - Viewport matrix testing, mobile-first patterns
  6. Test-Driven UX - E2E tests verify UX quality automatically

Implementation Roadmap:

  • 10-week phased approach from foundation to full testing
  • Clear verification commands at each phase
  • Commit references for specific patterns
  • Troubleshooting guide for common issues

Benefits for Other Products:

  • Consistent UX across all ByteLyst products
  • Reduced development time with shared primitives
  • Improved accessibility and responsive design
  • Automated testing ensures quality
  • AI-friendly reports enable continuous improvement

This guide enables other products to replicate the exact same UX implementation approach, leveraging common platform packages, design tokens, and Cipher design system principles.