From 3fe4f0786c72b9bfa982cc564af1eb8430bb47ff Mon Sep 17 00:00:00 2001 From: root Date: Sat, 9 May 2026 22:09:43 +0000 Subject: [PATCH] feat(ux): add UX testing setup guide and common platform integration --- .npmrc | 13 +- docs/UX_TESTING_SETUP_GUIDE.md | 872 +++++++++++++++++++++++++++ web/src/components/ui/Primitives.tsx | 2 +- 3 files changed, 885 insertions(+), 2 deletions(-) create mode 100644 docs/UX_TESTING_SETUP_GUIDE.md diff --git a/.npmrc b/.npmrc index 4f51e76..eb69750 100644 --- a/.npmrc +++ b/.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 diff --git a/docs/UX_TESTING_SETUP_GUIDE.md b/docs/UX_TESTING_SETUP_GUIDE.md new file mode 100644 index 0000000..901f296 --- /dev/null +++ b/docs/UX_TESTING_SETUP_GUIDE.md @@ -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 | 'link'; +type ProductButtonSize = NonNullable | 'icon'; +type ProductFieldVariant = 'surface' | 'muted'; +type ProductFieldSize = 'sm' | 'md'; +type ProductBadgeVariant = NonNullable | 'danger'; +type ProductStatusTone = 'success' | 'warning' | 'error' | 'info' | 'neutral'; + +// Extend interfaces with product-specific props +export interface ButtonProps extends Omit { + variant?: ProductButtonVariant; + size?: ProductButtonSize; +} + +export interface IconButtonProps extends Omit { + 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 = { + 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( + ({ variant = 'primary', size = 'md', className, ...props }, ref) => ( + + ), +); + +export const IconButton = React.forwardRef( + ({ icon, label, variant = 'ghost', size = 'icon', className, ...props }, ref) => ( + + ), +); + +export const Input = React.forwardRef( + ({ controlSize = 'md', variant = 'surface', className, ...props }, ref) => ( + + ), +); + +export function NotelettStatusBadge({ + status, + children, +}: { + status: NotelettStatus | string | null | undefined; + children?: React.ReactNode; +}) { + return ( + + {children ?? status ?? 'Unknown'} + + ); +} +``` + +**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 +Active + +// Replace .workspace-tag with Badge +Personal + +// Replace .status-pill with NotelettStatusBadge + +``` + +### Alert Banner Unification + +**Before:** Different alert implementations across components + +```typescript +// NotesList.tsx - custom alert +
+ ⚠️ + Warning message +
+ +// WorkspacesTab.tsx - different alert +
+
!
+
Warning text
+
+``` + +**After:** Shared AlertBanner component + +```typescript +import { AlertBanner } from '../components/ui/Primitives'; + +// Both components now use shared AlertBanner + + Warning message + + + + Error message + +``` + +### Table Controls Standardization + +**Before:** Custom table controls with inconsistent styling + +```typescript +// Different button styles across tables + + + +``` + +**After:** Standardized Button component + +```typescript +import { Button, IconButton } from '../components/ui/Primitives'; + +// Consistent button styles + + +} label="Complete" /> +``` + +--- + +## Part 4: Accessibility Improvements + +### Keyboard Navigation + +**Focus Management** + +```typescript +// Ensure interactive elements are focusable + +``` + +**Keyboard Toggles** + +```typescript +
{ + if (e.key === 'Enter' || e.key === ' ') { + e.preventDefault(); + toggle(); + } + }} + aria-pressed={isActive} +> + Toggle Option +
+``` + +### ARIA Labels + +**Button Labels** + +```typescript +// Icon buttons need explicit labels +} + label="Edit note" + aria-label="Edit note" +/> +``` + +**Status Indicators** + +```typescript +// Status badges need descriptive labels + + Active + +``` + +**Form Labels** + +```typescript +// All inputs need associated labels + + Note Title + + Enter a descriptive title for your note + +``` + +### 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 +

+ Note Title +

+

+ Section Header +

+

+ Note content +

+``` + +#### 2. Spacing System + +**Consistent Spacing** + +```typescript +// Use design token spacing +
+ Content +
+ +
+ + +
+``` + +#### 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)', +}; + +

Body text

+``` + +#### 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)', +}; + +
Success message
+``` + +--- + +## 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 \ No newline at end of file diff --git a/web/src/components/ui/Primitives.tsx b/web/src/components/ui/Primitives.tsx index af57bde..8063be5 100644 --- a/web/src/components/ui/Primitives.tsx +++ b/web/src/components/ui/Primitives.tsx @@ -362,4 +362,4 @@ export function EmptyState({ className, ...props }: EmptyStateProps) { export function LoadingSpinner({ className, ...props }: LoadingSpinnerProps) { return ; -} +} \ No newline at end of file