# Design System Remediation Playbook > **Purpose:** Agent-executable playbook. Every task has exact file paths, code templates, verification commands, and binary pass/fail criteria. A coding agent should be able to execute this document end-to-end without human intervention. > > **Companion doc:** `DESIGN_SYSTEM_AUDIT.md` — the audit findings this playbook remediates. > > **Commit convention:** `feat(design-system): ` or `fix(design-system): ` --- ## Prerequisites ```bash # All work happens relative to the workspace root WORKSPACE="$HOME/code/mygh" # Verify repos exist ls $WORKSPACE/learning_ai_common_plat # ← token generator lives here ls $WORKSPACE/learning_ai_clock # ChronoMind ls $WORKSPACE/learning_ai_fastgap # NomGap ls $WORKSPACE/learning_ai_jarvis_jr # JarvisJr ls $WORKSPACE/learning_ai_trails # ActionTrail ls $WORKSPACE/learning_ai_flowmonk # FlowMonk ls $WORKSPACE/learning_ai_notes # NoteLett ls $WORKSPACE/learning_ai_local_memory_gpt # LocalMemGPT ls $WORKSPACE/learning_ai_local_llms # Local LLM Lab # pnpm must be available (common-plat is a pnpm workspace) pnpm --version # must be >= 9 ``` ## Repo Map (Reference) | Alias | Repo | Web Source | CSS Prefix | Backend Port | | ------ | ------------------------------ | ------------------------- | ---------- | ------------ | | `PLAT` | `learning_ai_common_plat` | `packages/design-tokens/` | N/A | N/A | | `CM` | `learning_ai_clock` | `web/src/` | `--cm-` | 4011 | | `NG` | `learning_ai_fastgap` | `web/src/` | `--ng-` | 4013 | | `JJ` | `learning_ai_jarvis_jr` | `web/src/` | `--jj-` | 4012 | | `AT` | `learning_ai_trails` | `web/src/` | `--at-` | 4018 | | `FM` | `learning_ai_flowmonk` | `web/src/` | `--fm-` | 4017 | | `NL` | `learning_ai_notes` | `web/src/` | `--nl-` | 4016 | | `LMG` | `learning_ai_local_memory_gpt` | `web/src/` | `--lmg-` | 4019 | | `LLM` | `learning_ai_local_llms` | `dashboard/src/` | `--llm-` | 3000 | --- # Phase 1: Foundation Fix > **Goal:** Make the design token system actually consumable. Currently tokens are defined but no app imports them. > **Depends on:** Nothing — this is the starting phase. > **Estimated effort:** 1-2 days --- ## Task 1.1: Add Missing Product Palettes to Token JSON **File:** `$WORKSPACE/learning_ai_common_plat/packages/design-tokens/tokens/bytelyst.tokens.json` **What:** Add `color.actiontrail`, `color.notelett`, `color.localmemgpt`, and `color.localllmlab` sections. Currently only 7 of 11 products have palettes. **Action:** Open the token JSON and add these 4 new product palette objects at the same level as `color.chronomind`, `color.nomgap`, etc. Use the product's existing `globals.css` as the source of truth for color values. For each missing product, extract the product-specific colors from their `globals.css`: ```bash # ActionTrail colors (from learning_ai_trails/web/src/app/globals.css) grep -E '^\s+--at-' $WORKSPACE/learning_ai_trails/web/src/app/globals.css # NoteLett colors (from learning_ai_notes/web/src/app/globals.css, skip --ml- leaks) grep -E '^\s+--nl-' $WORKSPACE/learning_ai_notes/web/src/app/globals.css # LocalMemGPT colors (from learning_ai_local_memory_gpt/web/src/app/globals.css) grep -E '^\s+--lmg-' $WORKSPACE/learning_ai_local_memory_gpt/web/src/app/globals.css # Local LLM Lab colors (from learning_ai_local_llms/dashboard/src/app/globals.css) grep -E '^\s+--llm-' $WORKSPACE/learning_ai_local_llms/dashboard/src/app/globals.css ``` Add JSON entries following the existing pattern. Example for ActionTrail: ```json "actiontrail": { "bgCanvas": "#0B0D11", "bgElevated": "#12151C", "surfaceCard": "#171B24", "textPrimary": "#E8ECF4", "textSecondary": "#8B95A8", "accent": "#6C8EEF", "success": "#34D399", "warning": "#F59E0B", "danger": "#EF4444", "riskCritical": "#DC2626", "riskHigh": "#F97316", "riskMedium": "#EAB308", "riskLow": "#22C55E" } ``` Repeat for `notelett`, `localmemgpt`, `localllmlab` — extracting values from each app's `globals.css`. **Verify:** ```bash cd $WORKSPACE/learning_ai_common_plat node -e " const t = require('./packages/design-tokens/tokens/bytelyst.tokens.json'); const required = ['actiontrail','notelett','localmemgpt','localllmlab']; const found = required.filter(p => t.color[p]); console.log('Found:', found.length, '/', required.length); if (found.length !== required.length) { console.log('MISSING:', required.filter(p => !t.color[p])); process.exit(1); } console.log('PASS'); " ``` **Pass criteria:** All 4 products exist in `color.*` → script prints `PASS`. --- ## Task 1.2: Extend Token Generator for Per-Product CSS **File:** `$WORKSPACE/learning_ai_common_plat/packages/design-tokens/scripts/generate.ts` **What:** The current `generateCSS()` function hardcodes the `--ml-` prefix. Refactor it to accept a product name and prefix, then loop over all products. **Action:** Add a new function `generateProductCSS(productId, prefix, productColors)` and call it for every product that has a palette in the JSON. Also keep the existing `tokens.css` (shared/semantic) output. Add this mapping object near the top of `generate.ts` (after the `tokens` const): ```typescript const PRODUCT_CSS_MAP: Record = { mindlyst: { prefix: 'ml', colorsKey: 'brain' }, // existing (uses color.brain for gradients) chronomind: { prefix: 'cm', colorsKey: 'chronomind' }, jarvisjr: { prefix: 'jj', colorsKey: 'jarvisjr' }, nomgap: { prefix: 'ng', colorsKey: 'nomgap' }, actiontrail: { prefix: 'at', colorsKey: 'actiontrail' }, flowmonk: { prefix: 'fm', colorsKey: 'flowmonk' }, notelett: { prefix: 'nl', colorsKey: 'notelett' }, localmemgpt: { prefix: 'lmg', colorsKey: 'localmemgpt' }, localllmlab: { prefix: 'llm', colorsKey: 'localllmlab' }, lysnrai: { prefix: 'lys', colorsKey: 'lysnrai' }, peakpulse: { prefix: 'pp', colorsKey: 'peakpulse' }, }; ``` Add a new function: ```typescript function generateProductCSS(productId: string, prefix: string, colorsKey: string): string { const productColors = tokens.color[colorsKey]; if (!productColors) return `/* No palette found for ${productId} (color.${colorsKey}) */\n`; const lines: string[] = [ `/* Auto-generated ${productId} tokens from bytelyst.tokens.json — do not edit manually */`, '', ':root {', ]; // Semantic colors (dark as default) — shared across all products for (const [key, value] of Object.entries(tokens.color.semantic.dark)) { lines.push(` --${prefix}-${camelToKebab(key)}: ${value};`); } lines.push(''); // Product-specific colors lines.push(` /* ${productId} product colors */`); for (const [key, value] of Object.entries(productColors)) { if (typeof value === 'string') { lines.push(` --${prefix}-${camelToKebab(key)}: ${value};`); } else if (typeof value === 'object' && value !== null && 'from' in value) { // Gradient — emit from/to lines.push(` --${prefix}-${camelToKebab(key)}-from: ${(value as any).from};`); lines.push(` --${prefix}-${camelToKebab(key)}-to: ${(value as any).to};`); } } lines.push(''); // Typography for (const [key, value] of Object.entries(tokens.typography.fontFamily)) { const cssVal = typeof value === 'string' ? value.replace(/'/g, '"') : value; lines.push(` --${prefix}-font-${key}: ${cssVal};`); } lines.push(''); // Font sizes for (const [key, value] of Object.entries(tokens.typography.fontSize)) { lines.push(` --${prefix}-fs-${key}: ${value}px;`); } lines.push(''); // Spacing for (const [key, value] of Object.entries(tokens.spacing)) { lines.push(` --${prefix}-space-${key}: ${value === 0 ? '0' : `${value}px`};`); } lines.push(''); // Radius for (const [key, value] of Object.entries(tokens.radius)) { lines.push(` --${prefix}-radius-${key}: ${value}px;`); } lines.push(''); // Elevation for (const [key, value] of Object.entries(tokens.elevation)) { if (key === 'none') continue; lines.push(` --${prefix}-elevation-${key}: ${value};`); } lines.push(''); // Motion for (const [key, value] of Object.entries(tokens.motion.duration)) { if (key === 'instant') continue; lines.push(` --${prefix}-motion-${key}: ${value}ms;`); } lines.push(` --${prefix}-easing-standard: ${tokens.motion.easing.standard};`); lines.push('}', ''); // Light theme overrides lines.push('[data-theme="light"] {'); for (const [key, value] of Object.entries(tokens.color.semantic.light)) { const darkVal = (tokens.color.semantic.dark as Record)[key]; if (value !== darkVal) { lines.push(` --${prefix}-${camelToKebab(key)}: ${value};`); } } lines.push('}', ''); return lines.join('\n'); } ``` Update the "Write all" section at the bottom of `generate.ts`: ```typescript // ── Write all ──────────────────────────────────────────────────────── // Shared semantic tokens (backward compatible) writeFileSync(resolve(outDir, 'tokens.css'), generateCSS()); writeFileSync(resolve(outDir, 'tokens.ts'), generateTS()); writeFileSync(resolve(outDir, 'MindLystTokens.kt'), generateKotlin()); writeFileSync(resolve(outDir, 'MindLystTheme.swift'), generateSwift()); // Per-product CSS for (const [productId, config] of Object.entries(PRODUCT_CSS_MAP)) { const css = generateProductCSS(productId, config.prefix, config.colorsKey); writeFileSync(resolve(outDir, `${productId}.css`), css); } console.log( `Generated 4 shared + ${Object.keys(PRODUCT_CSS_MAP).length} product token files in generated/` ); ``` **Verify:** ```bash cd $WORKSPACE/learning_ai_common_plat/packages/design-tokens npx tsx scripts/generate.ts # Check per-product files exist for p in chronomind jarvisjr nomgap actiontrail flowmonk notelett localmemgpt localllmlab lysnrai peakpulse mindlyst; do if [ -f "generated/${p}.css" ]; then echo "PASS: ${p}.css exists"; else echo "FAIL: ${p}.css missing"; exit 1; fi done # Spot-check: chronomind.css should have --cm- prefix grep -c '\-\-cm-' generated/chronomind.css | xargs -I{} test {} -gt 20 && echo "PASS: --cm- prefix count > 20" || echo "FAIL" # Spot-check: nomgap.css should have --ng- prefix, NOT --ml- grep -c '\-\-ng-' generated/nomgap.css | xargs -I{} test {} -gt 20 && echo "PASS: --ng- prefix" || echo "FAIL" grep -c '\-\-ml-' generated/nomgap.css | xargs -I{} test {} -eq 0 && echo "PASS: no --ml- leak" || echo "FAIL: --ml- leak detected" ``` **Pass criteria:** 11 product CSS files generated. Each uses its correct prefix. No `--ml-` leaks in non-MindLyst files. **Commit:** `feat(design-tokens): per-product CSS generation for all 11 products` --- ## Task 1.3: Wire Web Apps to Import Generated Tokens **What:** Each product web app should import its generated CSS file from `@bytelyst/design-tokens` instead of hand-writing CSS custom properties. **This task is app-by-app.** For each app below: 1. Add `@bytelyst/design-tokens` to `package.json` (or verify it's a `file:` ref if not published) 2. Replace hand-written CSS custom properties in `globals.css` with an `@import` of the generated file 3. Keep any product-specific CSS that is NOT token-related (layout rules, component styles, etc.) ### Sub-task 1.3a: Publish design-tokens package First, ensure the package is consumable. Add an `exports` field to the design-tokens `package.json`: **File:** `$WORKSPACE/learning_ai_common_plat/packages/design-tokens/package.json` Ensure it has: ```json { "exports": { ".": "./generated/tokens.ts", "./css": "./generated/tokens.css", "./css/*": "./generated/*.css", "./tokens": "./generated/tokens.ts", "./kotlin": "./generated/MindLystTokens.kt", "./swift": "./generated/MindLystTheme.swift" } } ``` Run `pnpm build` in common-plat to ensure it builds cleanly. ### Sub-task 1.3b: Wire each web app For each product web app, add the dependency and import: **Pattern for each app:** 1. In the app's `package.json`, add: ```json "@bytelyst/design-tokens": "file:../../learning_ai_common_plat/packages/design-tokens" ``` (Adjust the relative path based on the repo location) 2. In `globals.css`, replace the hand-written `:root { --xx-... }` block with: ```css @import '@bytelyst/design-tokens/css/.css'; ``` 3. Keep any product-specific overrides or non-token CSS rules that follow the `:root` block. **App-specific instructions:** | App | `package.json` path | `globals.css` path | Import line | | ----------- | ----------------------------------------------- | ------------------------- | -------------------------------------------------------- | | ChronoMind | `learning_ai_clock/web/package.json` | `web/src/app/globals.css` | `@import "@bytelyst/design-tokens/css/chronomind.css";` | | NomGap | `learning_ai_fastgap/web/package.json` | `web/src/app/globals.css` | `@import "@bytelyst/design-tokens/css/nomgap.css";` | | JarvisJr | `learning_ai_jarvis_jr/web/package.json` | `web/src/app/globals.css` | `@import "@bytelyst/design-tokens/css/jarvisjr.css";` | | ActionTrail | `learning_ai_trails/web/package.json` | `web/src/app/globals.css` | `@import "@bytelyst/design-tokens/css/actiontrail.css";` | | FlowMonk | `learning_ai_flowmonk/web/package.json` | `web/src/app/globals.css` | `@import "@bytelyst/design-tokens/css/flowmonk.css";` | | NoteLett | `learning_ai_notes/web/package.json` | `web/src/app/globals.css` | `@import "@bytelyst/design-tokens/css/notelett.css";` | | LocalMemGPT | `learning_ai_local_memory_gpt/web/package.json` | `web/src/app/globals.css` | `@import "@bytelyst/design-tokens/css/localmemgpt.css";` | **Important:** When replacing `:root {}` blocks: - Remove ALL lines that define `--xx-bg-canvas`, `--xx-text-primary`, `--xx-font-*`, `--xx-space-*`, `--xx-radius-*`, etc. — these now come from the generated file - KEEP any product-specific CSS that is layout, animation, or component styling (e.g., `body { ... }`, `.sidebar { ... }`, `@keyframes`) - KEEP the `@import "tailwindcss";` line if present (it must stay) - If the app has a `[data-theme="light"]` block, remove it — the generated CSS includes light overrides **Verify (per app):** ```bash # Example for ChronoMind: cd $WORKSPACE/learning_ai_clock/web grep -c '@bytelyst/design-tokens' package.json # must be >= 1 grep -c 'design-tokens/css/chronomind' src/app/globals.css # must be >= 1 grep -c '\-\-cm-bg-canvas' src/app/globals.css # must be 0 (removed hand-written) npm run typecheck # must pass npm run build -- --webpack # must pass (use --webpack if needed per AGENTS.md) ``` **Pass criteria:** Each app's `globals.css` imports from `@bytelyst/design-tokens`, has zero hand-written token definitions, and builds cleanly. **Commit (per app):** `feat(design-system): wire web to generated design tokens` --- ## Task 1.4: Fix NomGap and NoteLett `--ml-*` CSS Prefix Leaks **Files:** - `$WORKSPACE/learning_ai_fastgap/web/src/app/globals.css` - `$WORKSPACE/learning_ai_notes/web/src/app/globals.css` **What:** Both apps have `--ml-*` prefixed CSS variables that should be `--ng-*` and `--nl-*` respectively. After Task 1.3, these should be replaced by the generated import, but verify no leaks remain. **Verify:** ```bash # NomGap: zero --ml- refs (should all be --ng-) grep -c '\-\-ml-' $WORKSPACE/learning_ai_fastgap/web/src/app/globals.css # Must be 0 # NoteLett: zero --ml- refs (should all be --nl-) grep -c '\-\-ml-' $WORKSPACE/learning_ai_notes/web/src/app/globals.css # Must be 0 ``` If any remain after Task 1.3, do a find-and-replace: `--ml-` → `--ng-` in NomGap, `--ml-` → `--nl-` in NoteLett. Then also update any component `.tsx` files that reference `--ml-*` variables: ```bash # Find all --ml- refs in NomGap web components grep -rn '\-\-ml-' $WORKSPACE/learning_ai_fastgap/web/src/ # Find all --ml- refs in NoteLett web components grep -rn '\-\-ml-' $WORKSPACE/learning_ai_notes/web/src/ ``` Replace each occurrence with the correct product prefix. **Pass criteria:** `grep -rn '\-\-ml-' /web/src/` returns 0 results for both NomGap and NoteLett. **Commit:** `fix(design-system): remove --ml- prefix leaks from NomGap and NoteLett` --- # Phase 2: Shared Component Library > **Goal:** Create `@bytelyst/ui` so product teams stop reinventing Button, Toast, Modal, etc. > **Depends on:** Phase 1 (tokens must be generated so components can consume them) > **Estimated effort:** 3-5 days --- ## Task 2.1: Scaffold `@bytelyst/ui` Package **Action:** Create the package in the common-plat monorepo. ```bash cd $WORKSPACE/learning_ai_common_plat mkdir -p packages/ui/src/components ``` **File:** `$WORKSPACE/learning_ai_common_plat/packages/ui/package.json` ```json { "name": "@bytelyst/ui", "version": "0.1.0", "type": "module", "exports": { ".": "./src/index.ts", "./button": "./src/components/Button.tsx", "./toast": "./src/components/Toast.tsx", "./modal": "./src/components/Modal.tsx", "./confirm-dialog": "./src/components/ConfirmDialog.tsx", "./badge": "./src/components/Badge.tsx", "./empty-state": "./src/components/EmptyState.tsx", "./sidebar": "./src/components/Sidebar.tsx" }, "peerDependencies": { "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0" }, "dependencies": { "@radix-ui/react-dialog": "^1.1.0", "@radix-ui/react-toast": "^1.2.0", "@radix-ui/react-alert-dialog": "^1.1.0", "@radix-ui/react-slot": "^1.1.0", "lucide-react": "^0.460.0", "clsx": "^2.1.0" }, "devDependencies": { "typescript": "^5.7.0", "@types/react": "^19.0.0", "@types/react-dom": "^19.0.0" } } ``` **File:** `$WORKSPACE/learning_ai_common_plat/packages/ui/tsconfig.json` ```json { "extends": "../../tsconfig.base.json", "compilerOptions": { "jsx": "react-jsx", "outDir": "dist", "rootDir": "src", "declaration": true }, "include": ["src"] } ``` **File:** `$WORKSPACE/learning_ai_common_plat/packages/ui/src/index.ts` ```typescript export { Button, type ButtonProps } from './components/Button.js'; export { Toast, ToastProvider, useToast, type ToastMessage } from './components/Toast.js'; export { Modal, type ModalProps } from './components/Modal.js'; export { ConfirmDialog, type ConfirmDialogProps } from './components/ConfirmDialog.js'; export { Badge, type BadgeProps } from './components/Badge.js'; export { EmptyState, type EmptyStateProps } from './components/EmptyState.js'; ``` **Verify:** ```bash cd $WORKSPACE/learning_ai_common_plat ls packages/ui/package.json && echo "PASS: package.json exists" ls packages/ui/src/index.ts && echo "PASS: index.ts exists" ``` --- ## Task 2.2: Build Button Component **File:** `$WORKSPACE/learning_ai_common_plat/packages/ui/src/components/Button.tsx` ```tsx import * as React from 'react'; import { Slot } from '@radix-ui/react-slot'; import { clsx } from 'clsx'; import { Loader2 } from 'lucide-react'; export interface ButtonProps extends React.ButtonHTMLAttributes { variant?: 'primary' | 'secondary' | 'ghost' | 'destructive' | 'outline'; size?: 'sm' | 'md' | 'lg'; loading?: boolean; asChild?: boolean; } export const Button = React.forwardRef( ( { variant = 'primary', size = 'md', loading, asChild, className, children, disabled, ...props }, ref ) => { const Comp = asChild ? Slot : 'button'; const baseStyles = 'inline-flex items-center justify-center font-medium rounded-md transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50'; const variants: Record = { primary: 'bg-[var(--bl-accent,#5A8CFF)] text-white hover:opacity-90', secondary: 'bg-[var(--bl-surface-card,#1a1a2e)] text-[var(--bl-text-primary,#fff)] border border-[var(--bl-border,#2a2a4a)] hover:bg-[var(--bl-surface-muted,#252540)]', ghost: 'text-[var(--bl-text-secondary,#a0a0b0)] hover:bg-[var(--bl-surface-muted,#252540)] hover:text-[var(--bl-text-primary,#fff)]', destructive: 'bg-red-600 text-white hover:bg-red-700', outline: 'border border-[var(--bl-border,#2a2a4a)] text-[var(--bl-text-primary,#fff)] hover:bg-[var(--bl-surface-muted,#252540)]', }; const sizes: Record = { sm: 'h-8 px-3 text-xs gap-1.5', md: 'h-10 px-4 text-sm gap-2', lg: 'h-12 px-6 text-base gap-2.5', }; return ( {loading && } {children} ); } ); Button.displayName = 'Button'; ``` **Verify:** ```bash cd $WORKSPACE/learning_ai_common_plat npx tsc --noEmit -p packages/ui/tsconfig.json 2>&1 | head -5 # Must have 0 errors (some warnings OK if peer deps not installed locally) ``` --- ## Task 2.3: Build Toast Component **File:** `$WORKSPACE/learning_ai_common_plat/packages/ui/src/components/Toast.tsx` ```tsx 'use client'; import * as React from 'react'; import { clsx } from 'clsx'; import { X, CheckCircle, AlertTriangle, Info, AlertCircle } from 'lucide-react'; export interface ToastMessage { id: string; type: 'success' | 'error' | 'warning' | 'info'; title: string; description?: string; duration?: number; } type ToastListener = (toasts: ToastMessage[]) => void; let globalToasts: ToastMessage[] = []; const listeners = new Set(); function notifyListeners() { listeners.forEach(l => l([...globalToasts])); } export function toast(msg: Omit) { const id = `toast-${Date.now()}-${Math.random().toString(36).slice(2, 7)}`; globalToasts = [...globalToasts, { ...msg, id }]; notifyListeners(); const duration = msg.duration ?? 5000; if (duration > 0) setTimeout(() => dismissToast(id), duration); } export function dismissToast(id: string) { globalToasts = globalToasts.filter(t => t.id !== id); notifyListeners(); } export function useToast() { return { toast, dismiss: dismissToast }; } const icons: Record = { success: , error: , warning: , info: , }; export function Toast({ message, onDismiss }: { message: ToastMessage; onDismiss: () => void }) { return (
{icons[message.type]}

{message.title}

{message.description && (

{message.description}

)}
); } export function ToastProvider({ children }: { children: React.ReactNode }) { const [toasts, setToasts] = React.useState([]); React.useEffect(() => { listeners.add(setToasts); return () => { listeners.delete(setToasts); }; }, []); return ( <> {children}
{toasts.map(t => ( dismissToast(t.id)} /> ))}
); } ``` --- ## Task 2.4: Build Modal Component **File:** `$WORKSPACE/learning_ai_common_plat/packages/ui/src/components/Modal.tsx` ```tsx 'use client'; import * as React from 'react'; import * as Dialog from '@radix-ui/react-dialog'; import { clsx } from 'clsx'; import { X } from 'lucide-react'; export interface ModalProps { open: boolean; onOpenChange: (open: boolean) => void; title: string; description?: string; size?: 'sm' | 'md' | 'lg' | 'full'; children: React.ReactNode; } const sizes: Record = { sm: 'max-w-sm', md: 'max-w-lg', lg: 'max-w-2xl', full: 'max-w-[90vw]', }; export function Modal({ open, onOpenChange, title, description, size = 'md', children, }: ModalProps) { return ( {title} {description && ( {description} )}
{children}
); } ``` --- ## Task 2.5: Build ConfirmDialog Component **File:** `$WORKSPACE/learning_ai_common_plat/packages/ui/src/components/ConfirmDialog.tsx` ```tsx 'use client'; import * as React from 'react'; import * as AlertDialog from '@radix-ui/react-alert-dialog'; import { clsx } from 'clsx'; import { AlertTriangle } from 'lucide-react'; import { Button } from './Button.js'; export interface ConfirmDialogProps { open: boolean; onOpenChange: (open: boolean) => void; title: string; description: string; confirmLabel?: string; cancelLabel?: string; variant?: 'destructive' | 'warning'; loading?: boolean; onConfirm: () => void | Promise; } export function ConfirmDialog({ open, onOpenChange, title, description, confirmLabel = 'Confirm', cancelLabel = 'Cancel', variant = 'destructive', loading, onConfirm, }: ConfirmDialogProps) { return (
{title} {description}
); } ``` --- ## Task 2.6: Build Badge Component **File:** `$WORKSPACE/learning_ai_common_plat/packages/ui/src/components/Badge.tsx` ```tsx import * as React from 'react'; import { clsx } from 'clsx'; export interface BadgeProps extends React.HTMLAttributes { variant?: 'success' | 'warning' | 'error' | 'info' | 'neutral'; size?: 'sm' | 'md'; dot?: boolean; } const variantStyles: Record = { success: 'bg-green-500/10 text-green-400 border-green-500/20', warning: 'bg-yellow-500/10 text-yellow-400 border-yellow-500/20', error: 'bg-red-500/10 text-red-400 border-red-500/20', info: 'bg-blue-500/10 text-blue-400 border-blue-500/20', neutral: 'bg-[var(--bl-surface-muted,#252540)] text-[var(--bl-text-secondary,#a0a0b0)] border-[var(--bl-border,#2a2a4a)]', }; export function Badge({ variant = 'neutral', size = 'sm', dot, className, children, ...props }: BadgeProps) { return ( {dot && ( )} {children} ); } ``` --- ## Task 2.7: Build EmptyState Component **File:** `$WORKSPACE/learning_ai_common_plat/packages/ui/src/components/EmptyState.tsx` ```tsx import * as React from 'react'; import { clsx } from 'clsx'; import { Inbox } from 'lucide-react'; import { Button } from './Button.js'; export interface EmptyStateProps { icon?: React.ReactNode; title: string; description?: string; actionLabel?: string; onAction?: () => void; className?: string; } export function EmptyState({ icon, title, description, actionLabel, onAction, className, }: EmptyStateProps) { return (
{icon ?? }

{title}

{description && (

{description}

)} {actionLabel && onAction && ( )}
); } ``` **Verify all components build:** ```bash cd $WORKSPACE/learning_ai_common_plat pnpm install # install new deps npx tsc --noEmit -p packages/ui/tsconfig.json # Must exit 0 with no errors echo "PASS: @bytelyst/ui type-checks cleanly" ``` **Commit:** `feat(ui): create @bytelyst/ui with Button, Toast, Modal, ConfirmDialog, Badge, EmptyState` --- # Phase 3: UX Quality Bar > **Goal:** Every product web app meets the minimum UX quality bar from the audit's non-negotiable checklist. > **Depends on:** Phase 2 (components from `@bytelyst/ui` are used here) > **Estimated effort:** 2-3 days --- ## Task 3.1: Add `not-found.tsx` to All Product Web Apps **What:** 6 of 7 product web apps have no custom 404 page. Users hitting a bad URL see the default Next.js 404. **Apps needing this file:** NomGap, JarvisJr, ActionTrail, FlowMonk, NoteLett, LocalMemGPT **Template:** Create `src/app/not-found.tsx` in each app. Use ChronoMind's as the reference pattern but replace the CSS prefix. For each app, create `not-found.tsx` at `/web/src/app/not-found.tsx`: ```tsx import Link from 'next/link'; export default function NotFound() { return (

404

Page not found

The page you are looking for does not exist or has been moved.

Go home
); } ``` **Prefix substitutions:** | App | Replace `PREFIX` with | | ----------- | --------------------- | | NomGap | `ng` | | JarvisJr | `jj` | | ActionTrail | `at` | | FlowMonk | `fm` | | NoteLett | `nl` | | LocalMemGPT | `lmg` | **Verify:** ```bash for repo in learning_ai_fastgap learning_ai_jarvis_jr learning_ai_trails learning_ai_flowmonk learning_ai_notes learning_ai_local_memory_gpt; do if [ -f "$WORKSPACE/$repo/web/src/app/not-found.tsx" ]; then echo "PASS: $repo"; else echo "FAIL: $repo"; fi done ``` **Pass criteria:** All 6 files exist. Build passes for each app. --- ## Task 3.2: Add `error.tsx` to All Product Web Apps **What:** Only ChronoMind has an error boundary. All others show the default Next.js error page. **Apps needing this file:** NomGap, JarvisJr, ActionTrail, FlowMonk, NoteLett, LocalMemGPT **Template:** Create `src/app/error.tsx` in each app: ```tsx 'use client'; import { useEffect } from 'react'; export default function Error({ error, reset, }: { error: Error & { digest?: string }; reset: () => void; }) { useEffect(() => { console.error('Unhandled error:', error); }, [error]); return (

Something went wrong

{error.message || 'An unexpected error occurred.'}

); } ``` Same prefix substitutions as Task 3.1. **Verify:** ```bash for repo in learning_ai_fastgap learning_ai_jarvis_jr learning_ai_trails learning_ai_flowmonk learning_ai_notes learning_ai_local_memory_gpt; do if [ -f "$WORKSPACE/$repo/web/src/app/error.tsx" ]; then echo "PASS: $repo"; else echo "FAIL: $repo"; fi done ``` --- ## Task 3.3: Add `loading.tsx` to All Product Web Apps **What:** Zero apps have a loading state for route transitions. **Apps needing this file:** ALL 7 product web apps + ChronoMind **Template:** Create `src/app/loading.tsx`: ```tsx export default function Loading() { return (

Loading...

); } ``` **Verify:** ```bash for repo in learning_ai_clock learning_ai_fastgap learning_ai_jarvis_jr learning_ai_trails learning_ai_flowmonk learning_ai_notes learning_ai_local_memory_gpt; do if [ -f "$WORKSPACE/$repo/web/src/app/loading.tsx" ]; then echo "PASS: $repo"; else echo "FAIL: $repo"; fi done ``` **Commit (Tasks 3.1-3.3):** `feat(design-system): add not-found, error, loading pages to all product web apps` --- ## Task 3.4: Add Focus-Visible CSS to All `globals.css` **What:** 5 of 7 apps have zero `focus-visible` refs. Keyboard users cannot see focus. **Action:** Add these rules to each app's `globals.css` (after the token import or `:root` block): ```css /* Focus-visible — keyboard accessibility */ *:focus-visible { outline: 2px solid var(--PREFIX-accent, #5a8cff); outline-offset: 2px; border-radius: 4px; } /* Remove default focus ring for mouse users */ *:focus:not(:focus-visible) { outline: none; } ``` **Files to modify** (use correct prefix for each): | File | Prefix | | ------------------------------------------------------ | ------ | | `learning_ai_fastgap/web/src/app/globals.css` | `ng` | | `learning_ai_jarvis_jr/web/src/app/globals.css` | `jj` | | `learning_ai_trails/web/src/app/globals.css` | `at` | | `learning_ai_flowmonk/web/src/app/globals.css` | `fm` | | `learning_ai_notes/web/src/app/globals.css` | `nl` | | `learning_ai_local_memory_gpt/web/src/app/globals.css` | `lmg` | | `learning_ai_local_llms/dashboard/src/app/globals.css` | `llm` | (ChronoMind already has 17 focus-visible refs — skip it.) **Verify:** ```bash for repo in learning_ai_fastgap learning_ai_jarvis_jr learning_ai_trails learning_ai_flowmonk learning_ai_notes learning_ai_local_memory_gpt; do count=$(grep -c 'focus-visible' "$WORKSPACE/$repo/web/src/app/globals.css" 2>/dev/null) if [ "$count" -ge 2 ]; then echo "PASS: $repo ($count refs)"; else echo "FAIL: $repo ($count refs)"; fi done ``` **Commit:** `feat(design-system): add focus-visible CSS to all product web apps` --- ## Task 3.5: Add ToastProvider to Apps Missing It **What:** 4 of 7 apps have no toast system (JarvisJr, FlowMonk, NoteLett, LocalMemGPT). **Action:** For each app missing toast: 1. Add `@bytelyst/ui` to `package.json` 2. Import and wrap in `layout.tsx` (or `providers.tsx` if it exists) In the app's root layout (e.g., `src/app/layout.tsx`), wrap `{children}` with ``: ```tsx import { ToastProvider } from '@bytelyst/ui/toast'; // In the layout body: {children}; ``` **Files to modify:** | App | Layout file | | ----------- | ----------------------------------------------------- | | JarvisJr | `learning_ai_jarvis_jr/web/src/app/layout.tsx` | | FlowMonk | `learning_ai_flowmonk/web/src/app/layout.tsx` | | NoteLett | `learning_ai_notes/web/src/app/layout.tsx` | | LocalMemGPT | `learning_ai_local_memory_gpt/web/src/app/layout.tsx` | **Verify:** ```bash for repo in learning_ai_jarvis_jr learning_ai_flowmonk learning_ai_notes learning_ai_local_memory_gpt; do count=$(grep -c 'ToastProvider' "$WORKSPACE/$repo/web/src/app/layout.tsx" 2>/dev/null) if [ "$count" -ge 1 ]; then echo "PASS: $repo"; else echo "FAIL: $repo"; fi done ``` **Commit:** `feat(design-system): add ToastProvider to JarvisJr, FlowMonk, NoteLett, LocalMemGPT` --- ## Task 3.6: Add Confirmation Dialogs Before Delete Actions **What:** 5 of 7 apps perform delete actions without confirmation. **Action:** For each app, find all delete/remove handlers and wrap them with `ConfirmDialog`: ```bash # Find delete actions across all product web apps for repo in learning_ai_clock learning_ai_fastgap learning_ai_jarvis_jr learning_ai_trails learning_ai_flowmonk learning_ai_notes learning_ai_local_memory_gpt; do echo "--- $repo ---" grep -rn 'delete\|remove\|destroy' "$WORKSPACE/$repo/web/src/" --include="*.tsx" -l 2>/dev/null done ``` For each file with a delete action, add: 1. A `useState` for the confirm dialog open state 2. Store the item-to-delete in state 3. Wrap the delete button's `onClick` to open the dialog instead of deleting directly 4. The `` component with the actual delete in `onConfirm` **Pattern:** ```tsx import { ConfirmDialog } from '@bytelyst/ui/confirm-dialog'; // In the component: const [deleteTarget, setDeleteTarget] = useState(null); // Replace: onClick={() => handleDelete(item.id)} // With: onClick={() => setDeleteTarget(item.id)} // Add before closing tag: { if (!open) setDeleteTarget(null); }} title="Delete item?" description="This action cannot be undone. The item will be permanently removed." confirmLabel="Delete" variant="destructive" onConfirm={() => { handleDelete(deleteTarget!); setDeleteTarget(null); }} />; ``` **Verify:** ```bash for repo in learning_ai_clock learning_ai_fastgap learning_ai_jarvis_jr learning_ai_trails learning_ai_flowmonk learning_ai_notes learning_ai_local_memory_gpt; do count=$(grep -rn 'ConfirmDialog' "$WORKSPACE/$repo/web/src/" --include="*.tsx" 2>/dev/null | wc -l) deletes=$(grep -rn 'delete\|remove' "$WORKSPACE/$repo/web/src/" --include="*.tsx" -l 2>/dev/null | wc -l) echo "$repo: $count ConfirmDialog refs, $deletes files with delete actions" done ``` **Pass criteria:** Every file that has a delete action also has a ConfirmDialog import. **Commit:** `feat(design-system): add ConfirmDialog before all destructive actions` --- ## Task 3.7: Add Empty States to All List Views **What:** Most apps show nothing when a list is empty. **Action:** For each app, find all list renders and add an `EmptyState` fallback: ```bash # Find list renders (map calls) across product web apps for repo in learning_ai_clock learning_ai_fastgap learning_ai_jarvis_jr learning_ai_trails learning_ai_flowmonk learning_ai_notes learning_ai_local_memory_gpt; do echo "--- $repo ---" grep -rn '\.map(' "$WORKSPACE/$repo/web/src/" --include="*.tsx" -l 2>/dev/null done ``` **Pattern:** For every `items.map(...)` render, add a guard: ```tsx import { EmptyState } from '@bytelyst/ui/empty-state'; // Replace: { items.map(item => ); } // With: { items.length === 0 ? ( { /* open create flow */ }} /> ) : ( items.map(item => ) ); } ``` **Verify:** ```bash for repo in learning_ai_clock learning_ai_fastgap learning_ai_jarvis_jr learning_ai_trails learning_ai_flowmonk learning_ai_notes learning_ai_local_memory_gpt; do count=$(grep -rn 'EmptyState' "$WORKSPACE/$repo/web/src/" --include="*.tsx" 2>/dev/null | wc -l) echo "$repo: $count EmptyState refs" done # Each app should have >= 1 ``` **Commit:** `feat(design-system): add EmptyState to all list views` --- # Phase 4: Accessibility > **Goal:** WCAG 2.1 AA baseline across all web surfaces. > **Depends on:** Phase 3 (components with ARIA are already in place) > **Estimated effort:** 2-3 days --- ## Task 4.1: Add `@axe-core/playwright` to All E2E Suites **Action:** For each product web app with Playwright specs: 1. Add the dependency: ```bash cd $WORKSPACE//web && npm install -D @axe-core/playwright ``` 2. Add an accessibility check to the first spec file. In each app's main E2E spec (e.g., `e2e/navigation.spec.ts`), add: ```typescript import AxeBuilder from '@axe-core/playwright'; test('should have no critical accessibility violations', async ({ page }) => { await page.goto('/'); const results = await new AxeBuilder({ page }).withTags(['wcag2a', 'wcag2aa']).analyze(); expect(results.violations.filter(v => v.impact === 'critical')).toHaveLength(0); }); ``` **Repos:** learning_ai_clock, learning_ai_fastgap, learning_ai_jarvis_jr, learning_ai_trails, learning_ai_flowmonk, learning_ai_notes, learning_ai_local_memory_gpt **Verify:** ```bash for repo in learning_ai_clock learning_ai_fastgap learning_ai_jarvis_jr learning_ai_trails learning_ai_flowmonk learning_ai_notes learning_ai_local_memory_gpt; do count=$(grep -rn 'axe-core\|AxeBuilder' "$WORKSPACE/$repo/web/e2e/" 2>/dev/null | wc -l) if [ "$count" -ge 1 ]; then echo "PASS: $repo"; else echo "FAIL: $repo"; fi done ``` **Commit:** `feat(design-system): add @axe-core/playwright a11y tests to all product web apps` --- ## Task 4.2: Add ARIA Labels to All Icon-Only Buttons **Action:** Find all ` // After: ``` Common labels: `"Close"`, `"Delete"`, `"Edit"`, `"Menu"`, `"Search"`, `"Settings"`, `"Filter"`, `"Refresh"`, `"Copy"`, `"Expand"`, `"Collapse"` **Verify:** ```bash for repo in learning_ai_clock learning_ai_fastgap learning_ai_jarvis_jr learning_ai_trails learning_ai_flowmonk learning_ai_notes learning_ai_local_memory_gpt; do total=$(grep -rn '/dev/null | wc -l) labeled=$(grep -rn '/dev/null | grep -c 'aria-label') echo "$repo: $labeled/$total buttons have aria-label" done ``` **Pass criteria:** >80% of buttons have `aria-label` (icon-only buttons must be 100%). **Commit:** `feat(design-system): add aria-label to all icon-only buttons` --- ## Task 4.3: Add Semantic `
` Landmark to All Layouts **Action:** Every app's main content area should be wrapped in `
`. Check each layout file: ```bash for repo in learning_ai_clock learning_ai_fastgap learning_ai_jarvis_jr learning_ai_trails learning_ai_flowmonk learning_ai_notes learning_ai_local_memory_gpt; do count=$(grep -rn '/dev/null | wc -l) echo "$repo: $count
refs" done ``` For any app with 0 refs, find the layout that wraps `{children}` and add `
`: ```tsx // Before:
{children}
// After:
{children}
``` **Verify:** Re-run the grep. All apps must have >= 1 `
` tag. **Commit:** `feat(design-system): add semantic
landmark to all web app layouts` --- # Phase 5: Polish > **Goal:** Elevate from functional to delightful — dark/light theme, responsive, visual regression, CI. > **Depends on:** Phases 1-4 > **Estimated effort:** 3-5 days --- ## Task 5.1: Add Dark/Light Theme Toggle to All Web Apps **What:** Only 3 of 7 apps have a theme toggle. All define dark tokens; most also have light tokens in the generated CSS. **Action:** Create a shared hook or copy the pattern from LocalMemGPT's `use-theme.ts` into each app. **Pattern** (create at `/web/src/lib/use-theme.ts`): ```typescript 'use client'; import { useState, useEffect, useCallback } from 'react'; type Theme = 'dark' | 'light' | 'system'; export function useTheme() { const [theme, setThemeState] = useState('system'); useEffect(() => { const saved = localStorage.getItem('theme') as Theme | null; if (saved) setThemeState(saved); }, []); const setTheme = useCallback((t: Theme) => { setThemeState(t); localStorage.setItem('theme', t); const resolved = t === 'system' ? window.matchMedia('(prefers-color-scheme: light)').matches ? 'light' : 'dark' : t; document.documentElement.setAttribute('data-theme', resolved); }, []); useEffect(() => { const resolved = theme === 'system' ? window.matchMedia('(prefers-color-scheme: light)').matches ? 'light' : 'dark' : theme; document.documentElement.setAttribute('data-theme', resolved); }, [theme]); return { theme, setTheme }; } ``` Add a toggle button in each app's settings or sidebar. **Verify:** ```bash for repo in learning_ai_clock learning_ai_fastgap learning_ai_jarvis_jr learning_ai_trails learning_ai_flowmonk learning_ai_notes learning_ai_local_memory_gpt; do if [ -f "$WORKSPACE/$repo/web/src/lib/use-theme.ts" ]; then echo "PASS: $repo"; else echo "FAIL: $repo"; fi done ``` **Commit:** `feat(design-system): add dark/light theme toggle to all product web apps` --- ## Task 5.2: Add Token Drift CI Check **What:** No CI job verifies that `bytelyst.tokens.json` changes result in regenerated output files. **Action:** Add a CI step to the common-plat CI workflow. **File:** `$WORKSPACE/learning_ai_common_plat/.gitea/workflows/ci.yml` (or `.github/workflows/`) Add this job: ```yaml token-drift: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: pnpm/action-setup@v4 - uses: actions/setup-node@v4 with: node-version: 22 cache: pnpm - run: pnpm install --frozen-lockfile - name: Regenerate tokens run: npx tsx packages/design-tokens/scripts/generate.ts - name: Check for drift run: | if git diff --exit-code packages/design-tokens/generated/; then echo "PASS: No token drift detected" else echo "FAIL: Token drift detected — run 'npx tsx packages/design-tokens/scripts/generate.ts' and commit" git diff packages/design-tokens/generated/ exit 1 fi ``` **Verify:** ```bash grep -c 'token-drift' $WORKSPACE/learning_ai_common_plat/.gitea/workflows/ci.yml # Must be >= 1 ``` **Commit:** `feat(ci): add token drift detection to CI pipeline` --- ## Task 5.3: Add Visual Regression Testing **What:** Zero apps have visual regression tests. **Action:** For each product web app with Playwright, add screenshot tests for key pages. In each app's E2E directory, add a spec: ```typescript // e2e/visual-regression.spec.ts import { test, expect } from '@playwright/test'; test.describe('visual regression', () => { test('dashboard matches snapshot', async ({ page }) => { await page.goto('/dashboard'); await page.waitForLoadState('networkidle'); await expect(page).toHaveScreenshot('dashboard.png', { maxDiffPixelRatio: 0.01 }); }); test('settings matches snapshot', async ({ page }) => { await page.goto('/settings'); await page.waitForLoadState('networkidle'); await expect(page).toHaveScreenshot('settings.png', { maxDiffPixelRatio: 0.01 }); }); }); ``` Adjust routes based on each app's actual pages (check `src/app/` for route names). **First run** will create baseline screenshots. Subsequent runs will compare against them. **Verify:** ```bash for repo in learning_ai_clock learning_ai_fastgap learning_ai_jarvis_jr learning_ai_trails learning_ai_flowmonk learning_ai_notes learning_ai_local_memory_gpt; do if [ -f "$WORKSPACE/$repo/web/e2e/visual-regression.spec.ts" ]; then echo "PASS: $repo"; else echo "FAIL: $repo"; fi done ``` **Commit:** `feat(design-system): add visual regression Playwright specs to all product web apps` --- ## Task 5.4: Add Design Review PR Template **What:** No repo requires screenshots for UI changes. **Action:** Create a PR template in every product repo: **File:** `/.github/pull_request_template.md` ```markdown ## Summary ## Screenshots | Before | After | | ------------------- | ------------------- | | | | ## Checklist - [ ] Design tokens used (no hardcoded hex) - [ ] Focus-visible works (keyboard nav) - [ ] ARIA labels on icon-only buttons - [ ] Empty state handled for list views - [ ] Confirmation dialog on destructive actions - [ ] Loading state on async operations - [ ] Dark/light theme tested ``` **Repos:** All 7 product repos + common-plat. **Verify:** ```bash for repo in learning_ai_clock learning_ai_fastgap learning_ai_jarvis_jr learning_ai_trails learning_ai_flowmonk learning_ai_notes learning_ai_local_memory_gpt learning_ai_common_plat; do if [ -f "$WORKSPACE/$repo/.github/pull_request_template.md" ]; then echo "PASS: $repo"; else echo "FAIL: $repo"; fi done ``` **Commit:** `feat(design-system): add design review PR template to all repos` --- # Final Verification Run this comprehensive check after all 5 phases are complete: ```bash WORKSPACE="$HOME/code/mygh" PASS=0; FAIL=0 echo "=== PHASE 1: Token Foundation ===" # All product palettes exist node -e "const t=require('$WORKSPACE/learning_ai_common_plat/packages/design-tokens/tokens/bytelyst.tokens.json'); const p=['actiontrail','notelett','localmemgpt','localllmlab']; const ok=p.every(x=>t.color[x]); console.log(ok?'PASS':'FAIL',': palettes'); process.exit(ok?0:1)" && ((PASS++)) || ((FAIL++)) # Per-product CSS files exist for p in chronomind jarvisjr nomgap actiontrail flowmonk notelett localmemgpt; do [ -f "$WORKSPACE/learning_ai_common_plat/packages/design-tokens/generated/${p}.css" ] && ((PASS++)) || { echo "FAIL: ${p}.css"; ((FAIL++)); } done # No --ml- leaks grep -rq '\-\-ml-' "$WORKSPACE/learning_ai_fastgap/web/src/app/globals.css" && { echo "FAIL: NomGap --ml- leak"; ((FAIL++)); } || ((PASS++)) grep -rq '\-\-ml-' "$WORKSPACE/learning_ai_notes/web/src/app/globals.css" && { echo "FAIL: NoteLett --ml- leak"; ((FAIL++)); } || ((PASS++)) echo "" echo "=== PHASE 2: Component Library ===" [ -f "$WORKSPACE/learning_ai_common_plat/packages/ui/src/components/Button.tsx" ] && ((PASS++)) || ((FAIL++)) [ -f "$WORKSPACE/learning_ai_common_plat/packages/ui/src/components/Toast.tsx" ] && ((PASS++)) || ((FAIL++)) [ -f "$WORKSPACE/learning_ai_common_plat/packages/ui/src/components/Modal.tsx" ] && ((PASS++)) || ((FAIL++)) [ -f "$WORKSPACE/learning_ai_common_plat/packages/ui/src/components/ConfirmDialog.tsx" ] && ((PASS++)) || ((FAIL++)) [ -f "$WORKSPACE/learning_ai_common_plat/packages/ui/src/components/Badge.tsx" ] && ((PASS++)) || ((FAIL++)) [ -f "$WORKSPACE/learning_ai_common_plat/packages/ui/src/components/EmptyState.tsx" ] && ((PASS++)) || ((FAIL++)) echo "" echo "=== PHASE 3: UX Quality Bar ===" for repo in learning_ai_fastgap learning_ai_jarvis_jr learning_ai_trails learning_ai_flowmonk learning_ai_notes learning_ai_local_memory_gpt; do [ -f "$WORKSPACE/$repo/web/src/app/not-found.tsx" ] && ((PASS++)) || { echo "FAIL: $repo not-found.tsx"; ((FAIL++)); } [ -f "$WORKSPACE/$repo/web/src/app/error.tsx" ] && ((PASS++)) || { echo "FAIL: $repo error.tsx"; ((FAIL++)); } [ -f "$WORKSPACE/$repo/web/src/app/loading.tsx" ] && ((PASS++)) || { echo "FAIL: $repo loading.tsx"; ((FAIL++)); } done echo "" echo "=== PHASE 4: Accessibility ===" for repo in learning_ai_clock learning_ai_fastgap learning_ai_jarvis_jr learning_ai_trails learning_ai_flowmonk learning_ai_notes learning_ai_local_memory_gpt; do grep -rq 'AxeBuilder\|axe-core' "$WORKSPACE/$repo/web/e2e/" 2>/dev/null && ((PASS++)) || { echo "FAIL: $repo axe-core"; ((FAIL++)); } grep -rq '/dev/null && ((PASS++)) || { echo "FAIL: $repo
"; ((FAIL++)); } done echo "" echo "=== PHASE 5: Polish ===" for repo in learning_ai_clock learning_ai_fastgap learning_ai_jarvis_jr learning_ai_trails learning_ai_flowmonk learning_ai_notes learning_ai_local_memory_gpt; do [ -f "$WORKSPACE/$repo/web/src/lib/use-theme.ts" ] && ((PASS++)) || { echo "FAIL: $repo use-theme"; ((FAIL++)); } [ -f "$WORKSPACE/$repo/web/e2e/visual-regression.spec.ts" ] && ((PASS++)) || { echo "FAIL: $repo visual-regression"; ((FAIL++)); } [ -f "$WORKSPACE/$repo/.github/pull_request_template.md" ] && ((PASS++)) || { echo "FAIL: $repo PR template"; ((FAIL++)); } done grep -q 'token-drift' "$WORKSPACE/learning_ai_common_plat/.gitea/workflows/ci.yml" && ((PASS++)) || { echo "FAIL: token drift CI"; ((FAIL++)); } echo "" echo "========================================" echo "RESULTS: $PASS PASS / $FAIL FAIL" echo "========================================" [ $FAIL -eq 0 ] && echo "ALL CHECKS PASSED" || echo "SOME CHECKS FAILED" ``` **Target:** 0 FAIL on all checks. --- _Generated by Windsurf Cascade — agent-executable design system remediation playbook._ _Companion audit: `DESIGN_SYSTEM_AUDIT.md`_