feat(ux): add UX testing setup guide and common platform integration
This commit is contained in:
parent
a983e044b1
commit
3fe4f0786c
13
.npmrc
13
.npmrc
@ -1,5 +1,16 @@
|
||||
# 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
|
||||
|
||||
# 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
|
||||
|
||||
@bytelyst:registry=http://${GITEA_NPM_HOST:-localhost}:3300/api/packages/ByteLyst/npm/
|
||||
//localhost:3300/api/packages/ByteLyst/npm/:_authToken=${GITEA_NPM_TOKEN}
|
||||
//${GITEA_NPM_HOST:-localhost}:3300}/api/packages/ByteLyst/npm/:_authToken=${GITEA_NPM_TOKEN}
|
||||
strict-ssl=false
|
||||
link-workspace-packages=true
|
||||
prefer-workspace-packages=true
|
||||
|
||||
872
docs/UX_TESTING_SETUP_GUIDE.md
Normal file
872
docs/UX_TESTING_SETUP_GUIDE.md
Normal file
@ -0,0 +1,872 @@
|
||||
# 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:** NoteLett - Agentic note-taking workspace for notes, tasks, relationships, and workspaces
|
||||
|
||||
---
|
||||
|
||||
## Overview
|
||||
|
||||
This guide documents the complete UX implementation approach for NoteLett, 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,
|
||||
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 for NoteLett
|
||||
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;
|
||||
}
|
||||
|
||||
// NoteLett-specific status mapping for badges
|
||||
export type NotelettStatus =
|
||||
| 'active' | 'archived' | 'pinned' | 'shared' | 'private'
|
||||
| 'draft' | 'published' | 'linked' | 'orphaned' | 'synced'
|
||||
| 'syncing' | 'error' | 'success' | 'warning' | 'info' | 'neutral';
|
||||
|
||||
const notelettStatusTone: Record<NotelettStatus, ProductStatusTone> = {
|
||||
active: 'success',
|
||||
archived: 'neutral',
|
||||
pinned: 'info',
|
||||
shared: 'success',
|
||||
private: 'neutral',
|
||||
draft: 'warning',
|
||||
published: 'success',
|
||||
linked: 'info',
|
||||
orphaned: 'warning',
|
||||
synced: 'success',
|
||||
syncing: 'info',
|
||||
error: 'error',
|
||||
success: 'success',
|
||||
warning: 'warning',
|
||||
info: 'info',
|
||||
neutral: 'neutral',
|
||||
};
|
||||
|
||||
// Helper function to map note status to tone
|
||||
export function statusToneFor(status: NotelettStatus | string | null | undefined): ProductStatusTone {
|
||||
if (!status) return 'neutral';
|
||||
const normalized = status.trim().toLowerCase().replace(/[\s_]+/g, '-') as NotelettStatus;
|
||||
return notelettStatusTone[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('notelett-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(--nl-input)]' : 'bg-[var(--nl-surface-muted)]',
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
),
|
||||
);
|
||||
|
||||
export function NotelettStatusBadge({
|
||||
status,
|
||||
children,
|
||||
}: {
|
||||
status: NotelettStatus | 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 NoteLett-specific prefixes:
|
||||
|
||||
```css
|
||||
/* Surface colors */
|
||||
background: var(--nl-surface-card);
|
||||
background: var(--nl-surface-muted);
|
||||
background: var(--nl-surface-overlay);
|
||||
|
||||
/* Text colors */
|
||||
color: var(--nl-text-primary);
|
||||
color: var(--nl-text-secondary);
|
||||
color: var(--nl-text-muted);
|
||||
|
||||
/* Border colors */
|
||||
border-color: var(--nl-border);
|
||||
border-color: var(--nl-border-muted);
|
||||
|
||||
/* Input styling */
|
||||
background: var(--nl-input);
|
||||
color: var(--nl-text-primary);
|
||||
border-color: var(--nl-border);
|
||||
|
||||
/* Focus states */
|
||||
border-color: var(--nl-focus-ring);
|
||||
box-shadow: 0 0 0 2px var(--nl-focus-ring-muted);
|
||||
|
||||
/* Semantic colors */
|
||||
color: var(--nl-accent);
|
||||
background: var(--nl-success);
|
||||
background: var(--nl-warning);
|
||||
background: var(--nl-error);
|
||||
|
||||
/* Spacing */
|
||||
padding: var(--nl-spacing-sm);
|
||||
padding: var(--nl-spacing-md);
|
||||
padding: var(--nl-spacing-lg);
|
||||
padding: var(--nl-spacing-xl);
|
||||
|
||||
/* Border radius */
|
||||
border-radius: var(--nl-radius-control);
|
||||
border-radius: var(--nl-radius-md);
|
||||
border-radius: var(--nl-radius-lg);
|
||||
|
||||
/* Typography */
|
||||
font-size: var(--nl-text-sm);
|
||||
font-size: var(--nl-text-base);
|
||||
font-size: var(--nl-text-lg);
|
||||
font-weight: var(--nl-font-medium);
|
||||
font-weight: var(--nl-font-semibold);
|
||||
```
|
||||
|
||||
### Component Token Patterns
|
||||
|
||||
**Example: Badge Component for NoteLett**
|
||||
|
||||
```typescript
|
||||
// Using design tokens for badge styling
|
||||
const badgeStyles = {
|
||||
success: {
|
||||
background: 'var(--nl-success-light)',
|
||||
color: 'var(--nl-success-dark)',
|
||||
borderColor: 'var(--nl-success)',
|
||||
},
|
||||
warning: {
|
||||
background: 'var(--nl-warning-light)',
|
||||
color: 'var(--nl-warning-dark)',
|
||||
borderColor: 'var(--nl-warning)',
|
||||
},
|
||||
error: {
|
||||
background: 'var(--nl-error-light)',
|
||||
color: 'var(--nl-error-dark)',
|
||||
borderColor: 'var(--nl-error)',
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Part 3: Component Normalization
|
||||
|
||||
### Badge Normalization
|
||||
|
||||
**Before:** One-off CSS classes for different badge styles
|
||||
|
||||
```css
|
||||
/* Old approach - one-off classes */
|
||||
.note-chip {
|
||||
background: #f0f0f0;
|
||||
padding: 4px 8px;
|
||||
border-radius: 4px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.workspace-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, NotelettStatusBadge } from '../components/ui/Primitives';
|
||||
|
||||
// Replace .note-chip with Badge
|
||||
<Badge variant="success">Active</Badge>
|
||||
|
||||
// Replace .workspace-tag with Badge
|
||||
<Badge variant="info">Personal</Badge>
|
||||
|
||||
// Replace .status-pill with NotelettStatusBadge
|
||||
<NotelettStatusBadge status="pinned" />
|
||||
```
|
||||
|
||||
### Alert Banner Unification
|
||||
|
||||
**Before:** Different alert implementations across components
|
||||
|
||||
```typescript
|
||||
// NotesList.tsx - custom alert
|
||||
<div className="alert-banner" style={{ background: '#fff3cd' }}>
|
||||
<span>⚠️</span>
|
||||
<span>Warning message</span>
|
||||
</div>
|
||||
|
||||
// WorkspacesTab.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="notes-filter-btn">Filter</button>
|
||||
<button className="task-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={<EditIcon />}
|
||||
label="Edit note"
|
||||
aria-label="Edit note"
|
||||
/>
|
||||
```
|
||||
|
||||
**Status Indicators**
|
||||
|
||||
```typescript
|
||||
// Status badges need descriptive labels
|
||||
<NotelettStatusBadge
|
||||
status="active"
|
||||
aria-label="Status: Active"
|
||||
>
|
||||
Active
|
||||
</NotelettStatusBadge>
|
||||
```
|
||||
|
||||
**Form Labels**
|
||||
|
||||
```typescript
|
||||
// All inputs need associated labels
|
||||
<Field>
|
||||
<FieldLabel htmlFor="title">Note Title</FieldLabel>
|
||||
<Input id="title" type="text" />
|
||||
<FieldDescription>Enter a descriptive title for your note</FieldDescription>
|
||||
</Field>
|
||||
```
|
||||
|
||||
### Focus Indicators
|
||||
|
||||
**Visible Focus States**
|
||||
|
||||
```css
|
||||
/* Ensure focus is visible */
|
||||
*:focus-visible {
|
||||
outline: 2px solid var(--nl-focus-ring);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
/* Or use design token */
|
||||
*:focus-visible {
|
||||
outline: 2px solid var(--nl-focus-ring);
|
||||
box-shadow: 0 0 0 2px var(--nl-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) {
|
||||
.notes-main {
|
||||
margin-left: 0;
|
||||
}
|
||||
.notes-right-panel {
|
||||
display: none;
|
||||
}
|
||||
.notes-sidebar {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
height: 60px;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Viewport Matrix Testing
|
||||
|
||||
**Test all routes across viewports**
|
||||
|
||||
```typescript
|
||||
const routes = ['/notes', '/workspaces', '/tasks', '/relationships'];
|
||||
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:3045',
|
||||
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
|
||||
├── 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
|
||||
```
|
||||
|
||||
### Test Runner Script
|
||||
|
||||
Create `scripts/tests/run-e2e.sh` with server lifecycle management:
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
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=3045
|
||||
|
||||
cd "$WEB_DIR"
|
||||
|
||||
# Kill existing server, start fresh, run tests, cleanup
|
||||
# ... (same as trading platform script)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Part 7: Cipher Design System Integration
|
||||
|
||||
### Design Principles
|
||||
|
||||
Follow Cipher design system principles for consistent UX across NoteLett:
|
||||
|
||||
#### 1. Visual Hierarchy
|
||||
|
||||
**Size and Weight**
|
||||
|
||||
```typescript
|
||||
// Use design tokens for consistent hierarchy
|
||||
<h1 style={{ fontSize: 'var(--nl-text-2xl)', fontWeight: 'var(--nl-font-bold)' }}>
|
||||
Note Title
|
||||
</h1>
|
||||
<h2 style={{ fontSize: 'var(--nl-text-xl)', fontWeight: 'var(--nl-font-semibold)' }}>
|
||||
Section Header
|
||||
</h2>
|
||||
<p style={{ fontSize: 'var(--nl-text-base)', color: 'var(--nl-text-secondary)' }}>
|
||||
Note content
|
||||
</p>
|
||||
```
|
||||
|
||||
#### 2. Spacing System
|
||||
|
||||
**Consistent Spacing**
|
||||
|
||||
```typescript
|
||||
// Use design token spacing
|
||||
<div style={{ padding: 'var(--nl-spacing-md)' }}>
|
||||
Content
|
||||
</div>
|
||||
|
||||
<div style={{ gap: 'var(--nl-spacing-sm)' }}>
|
||||
<Button>First</Button>
|
||||
<Button>Second</Button>
|
||||
</div>
|
||||
```
|
||||
|
||||
#### 3. Typography
|
||||
|
||||
**Font Scales**
|
||||
|
||||
```typescript
|
||||
// Use design token font sizes
|
||||
const textStyles = {
|
||||
xs: 'var(--nl-text-xs)',
|
||||
sm: 'var(--nl-text-sm)',
|
||||
base: 'var(--nl-text-base)',
|
||||
lg: 'var(--nl-text-lg)',
|
||||
xl: 'var(--nl-text-xl)',
|
||||
'2xl': 'var(--nl-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(--nl-success)',
|
||||
warning: 'var(--nl-warning)',
|
||||
error: 'var(--nl-error)',
|
||||
info: 'var(--nl-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
|
||||
- [ ] Configure Storybook
|
||||
|
||||
### 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
|
||||
- [ ] Set up Storybook
|
||||
- [ ] Integrate with CI
|
||||
|
||||
---
|
||||
|
||||
## Part 9: Verification Commands
|
||||
|
||||
### UI Audit
|
||||
|
||||
```bash
|
||||
# Check for raw controls and direct imports
|
||||
pnpm run audit:ui
|
||||
|
||||
# Strict audit (enforces token usage)
|
||||
pnpm run audit:ui:strict
|
||||
```
|
||||
|
||||
### 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
|
||||
./scripts/tests/run-e2e.sh
|
||||
|
||||
# Run specific test suites
|
||||
cd web
|
||||
pnpm test:e2e:viewport
|
||||
pnpm test:e2e:overflow
|
||||
pnpm test:e2e:ui
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Summary
|
||||
|
||||
This guide enables NoteLett to replicate the exact same UX implementation approach as the trading dashboard, leveraging common platform packages, design tokens, and Cipher design system principles.
|
||||
|
||||
**Key Benefits:**
|
||||
- Consistent UX across ByteLyst products
|
||||
- Reduced development time with shared primitives
|
||||
- Improved accessibility and responsive design
|
||||
- Automated testing ensures quality
|
||||
- AI-friendly reports enable continuous improvement
|
||||
|
||||
**Product-Specific Adaptations:**
|
||||
- NoteLett status mapping (active, archived, pinned, shared, etc.)
|
||||
- Note-specific design tokens (--nl-* prefix)
|
||||
- Note-taking workflow focused test cases
|
||||
- Workspace and relationship management patterns
|
||||
Loading…
Reference in New Issue
Block a user