diff --git a/.claude/commands/design-os/add-accessibility.md b/.claude/commands/design-os/add-accessibility.md new file mode 100644 index 0000000..7d44f3c --- /dev/null +++ b/.claude/commands/design-os/add-accessibility.md @@ -0,0 +1,235 @@ +# Add Accessibility + +You are helping the user analyze and document accessibility for a section of their product. This will analyze existing screen designs and generate a WCAG-based accessibility checklist and recommendations. + +## Step 1: Check Prerequisites + +First, identify the target section and verify that `spec.md`, `data.json`, `types.ts`, and screen designs all exist. + +Read `/product/product-roadmap.md` to get the list of available sections. + +If there's only one section, auto-select it. If there are multiple sections, use the AskUserQuestion tool to ask which section the user wants to analyze for accessibility. + +Then verify all required files exist: + +- `product/sections/[section-id]/spec.md` +- `product/sections/[section-id]/data.json` +- `product/sections/[section-id]/types.ts` +- At least one screen design component in `src/sections/[section-id]/` + +If spec.md doesn't exist: + +"I don't see a specification for **[Section Title]** yet. Please run `/shape-section` first to define the section's requirements." + +If data.json or types.ts don't exist: + +"I don't see sample data for **[Section Title]** yet. Please run `/sample-data` first to create sample data and types." + +If no screen designs exist: + +"I don't see any screen designs for **[Section Title]** yet. Please run `/design-screen` first to create screen designs, then come back to analyze accessibility." + +Stop here if any file is missing. + +## Step 2: Analyze Screen Designs + +Read all screen design components from `src/sections/[section-id]/components/*.tsx` and the preview wrappers from `src/sections/[section-id]/*.tsx`. + +For each screen design component, analyze: + +### WCAG Level A (Basic Requirements) +- **Semantic HTML**: Are proper HTML elements used (button, nav, main, etc.)? +- **Alt Text**: Are images provided with meaningful alt attributes? +- **Form Labels**: Are all form inputs properly labeled? +- **Keyboard Navigation**: Can all interactive elements be reached via keyboard? +- **Focus Indicators**: Are focus states visible? +- **Page Titles**: Are meaningful titles provided? +- **Language**: Is the page language declared? + +### WCAG Level AA (Standard Requirements) +- **Color Contrast**: Do text colors meet 4.5:1 contrast ratio (3:1 for large text)? +- **Text Resizing**: Can text be resized up to 200% without loss of functionality? +- **Multiple Ways**: Are there multiple ways to navigate (if applicable)? +- **Headings**: Are headings used in a logical hierarchy? +- **Focus Order**: Does the focus order make sense? +- **Error Identification**: Are form errors clearly identified? +- **Labels**: Are labels associated with inputs (not just placeholder text)? + +### WCAG Level AAA (Enhanced Requirements) +- **Enhanced Contrast**: Do text colors meet 7:1 contrast ratio (4.5:1 for large text)? +- **Sign Language**: Is sign language interpretation provided (if applicable)? +- **Extended Audio**: Is extended audio description provided (if applicable)? +- **Context-Sensitive Help**: Is help available when needed? + +### Additional Analysis +- **ARIA Labels**: Are ARIA labels used appropriately where needed? +- **ARIA Roles**: Are ARIA roles used correctly? +- **ARIA States**: Are ARIA states (aria-expanded, aria-selected, etc.) used properly? +- **Skip Links**: Are skip navigation links provided? +- **Error Messages**: Are error messages accessible and descriptive? +- **Loading States**: Are loading states announced to screen readers? +- **Empty States**: Are empty states clearly communicated? + +## Step 3: Generate Accessibility Report + +Create a comprehensive accessibility report in markdown format. The report should include: + +### Overview Section +- Summary of overall accessibility status +- Number of screen designs analyzed +- High-level compliance status (Level A, AA, AAA) +- Priority issues that need immediate attention + +### WCAG Compliance Checklist + +Organize findings by WCAG level: + +**Level A (Basic)** +- [ ] Semantic HTML structure +- [ ] Image alt text +- [ ] Form labels +- [ ] Keyboard navigation +- [ ] Focus indicators +- [ ] Page titles +- [ ] Language declaration + +**Level AA (Standard)** +- [ ] Color contrast (4.5:1 for normal text, 3:1 for large text) +- [ ] Text resizing (up to 200%) +- [ ] Heading hierarchy +- [ ] Focus order +- [ ] Error identification +- [ ] Input labels + +**Level AAA (Enhanced)** +- [ ] Enhanced contrast (7:1 for normal text, 4.5:1 for large text) +- [ ] Context-sensitive help + +### Screen-Specific Recommendations + +For each screen design, provide: +- Screen name +- Specific issues found +- Recommendations for fixes +- Priority level (High, Medium, Low) + +### Action Items + +Create a prioritized list of action items: +- **High Priority**: Issues that block basic accessibility +- **Medium Priority**: Issues that improve accessibility significantly +- **Low Priority**: Enhancements and optimizations + +## Step 4: Create the Accessibility File + +Create the file at `product/sections/[section-id]/accessibility.md` with this exact format: + +```markdown +# Accessibility Report for [Section Title] + +## Overview + +[2-3 sentence summary of accessibility status] + +**Analysis Date:** [Current date] +**Screen Designs Analyzed:** [Number] +**Overall Compliance:** [Level A / AA / AAA status] + +## WCAG Compliance Checklist + +### Level A (Basic Requirements) + +- [ ] Semantic HTML structure +- [ ] Image alt text provided +- [ ] Form inputs properly labeled +- [ ] Keyboard navigation functional +- [ ] Focus indicators visible +- [ ] Page titles meaningful +- [ ] Language declared + +### Level AA (Standard Requirements) + +- [ ] Color contrast meets 4.5:1 (normal text) / 3:1 (large text) +- [ ] Text resizable up to 200% +- [ ] Logical heading hierarchy +- [ ] Focus order makes sense +- [ ] Form errors clearly identified +- [ ] Input labels associated (not just placeholders) + +### Level AAA (Enhanced Requirements) + +- [ ] Enhanced contrast meets 7:1 (normal text) / 4.5:1 (large text) +- [ ] Context-sensitive help available + +## Screen-Specific Recommendations + +### [Screen Name 1] + +**Issues Found:** +- [Issue description with specific component/location] +- [Another issue] + +**Recommendations:** +- [Specific fix recommendation] +- [Another recommendation] + +**Priority:** [High/Medium/Low] + +### [Screen Name 2] + +[Repeat for each screen design] + +## Action Items + +### High Priority +1. [Action item] +2. [Action item] + +### Medium Priority +1. [Action item] +2. [Action item] + +### Low Priority +1. [Action item] +2. [Action item] + +## Notes + +[Any additional context or considerations] +``` + +## Step 5: Confirm and Next Steps + +Let the user know: + +"I've analyzed the accessibility of **[Section Title]** and created an accessibility report: + +**Report Created:** +- `product/sections/[section-id]/accessibility.md` - WCAG compliance checklist and recommendations + +**Summary:** +- Analyzed [X] screen design(s) +- Overall compliance: [Status] +- [X] high priority issues identified +- [X] medium priority issues identified +- [X] low priority enhancements suggested + +**Key Findings:** +- [Highlight 1-2 most important findings] + +**Next Steps:** +- Review the accessibility report in the section page +- Address high priority issues in your screen designs +- Run `/add-accessibility` again after making changes to update the report +- When all sections are complete, run `/export-product` to generate the complete export package" + +## Important Notes + +- Be thorough but practical - focus on actionable recommendations +- Prioritize issues that have the biggest impact on accessibility +- Provide specific code examples when suggesting fixes +- Reference WCAG guidelines when appropriate +- Consider both automated checks and manual review insights +- The report should be useful for developers implementing the designs +- Update the report format if the user requests changes + diff --git a/agents.md b/agents.md index 29086b6..2af1cbd 100644 --- a/agents.md +++ b/agents.md @@ -53,6 +53,7 @@ Design the persistent navigation and layout that wraps all sections. - `/shape-section` — Define the specification - `/sample-data` — Create sample data and types - `/design-screen` — Create screen designs +- `/add-accessibility` — Analyze and document accessibility (optional) - `/screenshot-design` — Capture screenshots ### 7. Export (`/export-product`) @@ -83,6 +84,7 @@ product/ # Product definition (portable) ├── spec.md # Section specification ├── data.json # Sample data for screen designs ├── types.ts # TypeScript interfaces + ├── accessibility.md # Accessibility report and checklist └── *.png # Screenshots src/ diff --git a/docs/design-section.md b/docs/design-section.md index fa6918d..17d92ce 100644 --- a/docs/design-section.md +++ b/docs/design-section.md @@ -103,7 +103,29 @@ If the spec implies multiple views (list view, detail view, form, etc.), you'll **Important:** Restart your dev server after creating screen designs to see the changes. -## 4. Capture Screenshots (Optional) +## 4. Add Accessibility (Optional) + +``` +/add-accessibility +``` + +Analyze your screen designs for accessibility compliance and generate a WCAG-based checklist and recommendations. + +This command: +1. Analyzes all screen design components in the section +2. Checks for common accessibility issues (ARIA labels, keyboard navigation, color contrast, semantic HTML, etc.) +3. Generates a comprehensive accessibility report with: + - WCAG compliance checklist (Level A, AA, AAA) + - Screen-specific recommendations + - Priority-ordered action items + +The accessibility report helps ensure your designs are accessible to all users and provides actionable guidance for improvements. + +**Requires:** Screen designs must exist (run `/design-screen` first) + +**Creates:** `product/sections/[section-id]/accessibility.md` + +## 5. Capture Screenshots (Optional) ``` /screenshot-design diff --git a/docs/usage.md b/docs/usage.md index d3159fd..0b59f56 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -25,7 +25,8 @@ Once the foundation is set, work through each section: 1. **Shape the Section** — Define scope and requirements 2. **Create Sample Data** — Generate realistic data and types 3. **Design the Screen** — Build the actual React components -4. **Capture Screenshots** — Document the design (optional) +4. **Add Accessibility** — Analyze and document accessibility (optional) +5. **Capture Screenshots** — Document the design (optional) Repeat for each section in your roadmap. @@ -51,6 +52,7 @@ See [Export](export.md) for details on what's included and how to use it. | `/shape-section` | Define a section's scope and requirements | | `/sample-data` | Generate sample data and TypeScript types | | `/design-screen` | Create screen design components | +| `/add-accessibility` | Analyze and document accessibility | | `/screenshot-design` | Capture screenshots | | `/export-product` | Generate the complete handoff package | diff --git a/src/components/AccessibilityCard.tsx b/src/components/AccessibilityCard.tsx new file mode 100644 index 0000000..8cf3069 --- /dev/null +++ b/src/components/AccessibilityCard.tsx @@ -0,0 +1,266 @@ +import { useState } from 'react' +import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card' +import { Collapsible, CollapsibleContent, CollapsibleTrigger } from '@/components/ui/collapsible' +import { ChevronDown, CheckCircle2, AlertCircle, XCircle, Circle } from 'lucide-react' +import { EmptyState } from '@/components/EmptyState' +import type { AccessibilityInfo } from '@/types/section' + +interface AccessibilityCardProps { + accessibility: AccessibilityInfo | null +} + +function getStatusIcon(status: 'pass' | 'warning' | 'fail' | 'not-checked') { + switch (status) { + case 'pass': + return + case 'warning': + return + case 'fail': + return + default: + return + } +} + +function getStatusLabel(status: 'pass' | 'warning' | 'fail' | 'not-checked') { + switch (status) { + case 'pass': + return 'Pass' + case 'warning': + return 'Warning' + case 'fail': + return 'Fail' + default: + return 'Not checked' + } +} + +function getComplianceSummary(checklist: AccessibilityInfo['checklist']) { + const byLevel = { + A: checklist.filter(item => item.level === 'A'), + AA: checklist.filter(item => item.level === 'AA'), + AAA: checklist.filter(item => item.level === 'AAA'), + } + + const passed = { + A: byLevel.A.filter(item => item.status === 'pass').length, + AA: byLevel.AA.filter(item => item.status === 'pass').length, + AAA: byLevel.AAA.filter(item => item.status === 'pass').length, + } + + const total = { + A: byLevel.A.length, + AA: byLevel.AA.length, + AAA: byLevel.AAA.length, + } + + return { passed, total, byLevel } +} + +export function AccessibilityCard({ accessibility }: AccessibilityCardProps) { + const [reportOpen, setReportOpen] = useState(false) + const [levelAOpen, setLevelAOpen] = useState(true) + const [levelAAOpen, setLevelAAOpen] = useState(false) + const [levelAAAOpen, setLevelAAAOpen] = useState(false) + + // Empty state + if (!accessibility) { + return + } + + const summary = getComplianceSummary(accessibility.checklist) + + return ( + + +
+ + Accessibility + + {accessibility.lastAnalyzed && ( + + Analyzed {new Date(accessibility.lastAnalyzed).toLocaleDateString()} + + )} +
+
+ + {/* Compliance Summary */} + {accessibility.checklist.length > 0 && ( +
+
+
+ Level A +
+
+ {summary.passed.A}/{summary.total.A} +
+
+
+
+ Level AA +
+
+ {summary.passed.AA}/{summary.total.AA} +
+
+
+
+ Level AAA +
+
+ {summary.passed.AAA}/{summary.total.AAA} +
+
+
+ )} + + {/* WCAG Checklist by Level */} + {summary.byLevel.A.length > 0 && ( + + + + Level A (Basic Requirements) + + ({summary.passed.A}/{summary.total.A} passed) + + + + + +
    + {summary.byLevel.A.map((item) => ( +
  • +
    + {getStatusIcon(item.status)} +
    +
    + + {item.description} + + {item.screen && ( + + ({item.screen}) + + )} +
    +
  • + ))} +
+
+
+ )} + + {summary.byLevel.AA.length > 0 && ( + + + + Level AA (Standard Requirements) + + ({summary.passed.AA}/{summary.total.AA} passed) + + + + + +
    + {summary.byLevel.AA.map((item) => ( +
  • +
    + {getStatusIcon(item.status)} +
    +
    + + {item.description} + + {item.screen && ( + + ({item.screen}) + + )} +
    +
  • + ))} +
+
+
+ )} + + {summary.byLevel.AAA.length > 0 && ( + + + + Level AAA (Enhanced Requirements) + + ({summary.passed.AAA}/{summary.total.AAA} passed) + + + + + +
    + {summary.byLevel.AAA.map((item) => ( +
  • +
    + {getStatusIcon(item.status)} +
    +
    + + {item.description} + + {item.screen && ( + + ({item.screen}) + + )} +
    +
  • + ))} +
+
+
+ )} + + {/* Full Report - Collapsible */} + {accessibility.report && ( + + + + + {reportOpen ? 'Hide' : 'View'} Full Report + + + +
+
+                  {accessibility.report}
+                
+
+
+
+ )} +
+
+ ) +} + diff --git a/src/components/EmptyState.tsx b/src/components/EmptyState.tsx index 6a8e4d0..797af4a 100644 --- a/src/components/EmptyState.tsx +++ b/src/components/EmptyState.tsx @@ -1,7 +1,7 @@ -import { FileText, Map, ClipboardList, Database, Layout, Package, Boxes, Palette, PanelLeft } from 'lucide-react' +import { FileText, Map, ClipboardList, Database, Layout, Package, Boxes, Palette, PanelLeft, Accessibility } from 'lucide-react' import { Card, CardContent } from '@/components/ui/card' -type EmptyStateType = 'overview' | 'roadmap' | 'spec' | 'data' | 'screen-designs' | 'data-model' | 'design-system' | 'shell' | 'export' +type EmptyStateType = 'overview' | 'roadmap' | 'spec' | 'data' | 'screen-designs' | 'data-model' | 'design-system' | 'shell' | 'export' | 'accessibility' interface EmptyStateProps { type: EmptyStateType @@ -67,6 +67,12 @@ const config: Record | null): StepStatus[] { const hasSpec = !!sectionData?.specParsed const hasData = !!sectionData?.data const hasScreenDesigns = !!(sectionData?.screenDesigns && sectionData.screenDesigns.length > 0) + const hasAccessibility = !!sectionData?.accessibility const hasScreenshots = !!(sectionData?.screenshots && sectionData.screenshots.length > 0) - const steps: boolean[] = [hasSpec, hasData, hasScreenDesigns, hasScreenshots] + const steps: boolean[] = [hasSpec, hasData, hasScreenDesigns, hasAccessibility, hasScreenshots] const firstIncomplete = steps.findIndex((done) => !done) return steps.map((done, index) => { @@ -144,8 +146,13 @@ export function SectionPage() { )} - {/* Step 4: Screenshots */} - + {/* Step 4: Accessibility */} + + + + + {/* Step 5: Screenshots */} + {!sectionData?.screenshots || sectionData.screenshots.length === 0 ? ( @@ -214,7 +221,7 @@ export function SectionPage() { {/* Next Step - shown when required steps (Spec, Data, Screen Designs) are complete */} {requiredStepsComplete && ( - +
{/* If there's a next section, show two options */} {nextSection ? ( diff --git a/src/lib/section-loader.ts b/src/lib/section-loader.ts index 93f7353..5426008 100644 --- a/src/lib/section-loader.ts +++ b/src/lib/section-loader.ts @@ -7,7 +7,7 @@ * - src/sections/[section-id]/[PageName].tsx - Screen design pages */ -import type { SectionData, ParsedSpec, ScreenDesignInfo, ScreenshotInfo } from '@/types/section' +import type { SectionData, ParsedSpec, ScreenDesignInfo, ScreenshotInfo, AccessibilityInfo, AccessibilityChecklistItem } from '@/types/section' import type { ComponentType } from 'react' // Load spec.md files from product/sections at build time @@ -35,6 +35,13 @@ const screenshotFiles = import.meta.glob('/product/sections/*/*.png', { eager: true, }) as Record +// Load accessibility.md files from product/sections at build time +const accessibilityFiles = import.meta.glob('/product/sections/*/accessibility.md', { + query: '?raw', + import: 'default', + eager: true, +}) as Record + /** * Extract section ID from a product/sections file path * e.g., "/product/sections/invoices/spec.md" -> "invoices" @@ -188,6 +195,89 @@ export function getSectionScreenshots(sectionId: string): ScreenshotInfo[] { return screenshots } +/** + * Parse accessibility.md content into AccessibilityInfo structure + * + * Expected format: + * # Accessibility Report for [Section Title] + * + * ## Overview + * [Description] + * + * **Analysis Date:** [Date] + * **Screen Designs Analyzed:** [Number] + * **Overall Compliance:** [Status] + * + * ## WCAG Compliance Checklist + * ### Level A (Basic Requirements) + * - [ ] Item 1 + * - [x] Item 2 + * ... + */ +export function parseAccessibility(md: string): AccessibilityInfo | null { + if (!md || !md.trim()) return null + + try { + // Extract analysis date + const dateMatch = md.match(/\*\*Analysis Date:\*\*\s*(.+)/i) + const lastAnalyzed = dateMatch?.[1]?.trim() || null + + // Extract checklist items from WCAG Compliance Checklist section + const checklist: AccessibilityChecklistItem[] = [] + + // Match Level A, AA, AAA sections + const levelSections = md.matchAll(/### Level (A{1,3})[^#]*\n([\s\S]*?)(?=\n## |\n### |$)/g) + + for (const match of levelSections) { + const level = match[1] as 'A' | 'AA' | 'AAA' + const content = match[2] + + // Extract checklist items (lines starting with - [ ] or - [x]) + const itemLines = content.split('\n').filter(line => { + const trimmed = line.trim() + return trimmed.startsWith('- [') && (trimmed.includes(']') || trimmed.includes('x]')) + }) + + for (const line of itemLines) { + const trimmed = line.trim() + const isChecked = trimmed.includes('[x]') || trimmed.includes('[X]') + const description = trimmed.replace(/^-\s*\[[xX\s]\]\s*/, '').trim() + + if (description) { + checklist.push({ + id: `${level}-${description.toLowerCase().replace(/\s+/g, '-').slice(0, 50)}`, + level, + description, + status: isChecked ? 'pass' : 'not-checked', + }) + } + } + } + + return { + report: md, + lastAnalyzed, + checklist, + } + } catch { + return null + } +} + +/** + * Get accessibility information for a specific section + */ +export function getSectionAccessibility(sectionId: string): AccessibilityInfo | null { + const accessibilityPath = `/product/sections/${sectionId}/accessibility.md` + const accessibilityContent = accessibilityFiles[accessibilityPath] || null + + if (!accessibilityContent) { + return null + } + + return parseAccessibility(accessibilityContent) +} + /** * Load screen design component dynamically */ @@ -217,6 +307,7 @@ export function loadSectionData(sectionId: string): SectionData { data, screenDesigns: getSectionScreenDesigns(sectionId), screenshots: getSectionScreenshots(sectionId), + accessibility: getSectionAccessibility(sectionId), } } diff --git a/src/types/section.ts b/src/types/section.ts index 8ef8d11..77d29b2 100644 --- a/src/types/section.ts +++ b/src/types/section.ts @@ -9,6 +9,7 @@ export interface SectionData { data: Record | null screenDesigns: ScreenDesignInfo[] screenshots: ScreenshotInfo[] + accessibility: AccessibilityInfo | null } export interface ParsedSpec { @@ -31,3 +32,17 @@ export interface ScreenshotInfo { path: string url: string } + +export interface AccessibilityInfo { + report: string | null + lastAnalyzed: string | null + checklist: AccessibilityChecklistItem[] +} + +export interface AccessibilityChecklistItem { + id: string + level: 'A' | 'AA' | 'AAA' + description: string + status: 'pass' | 'warning' | 'fail' | 'not-checked' + screen?: string +}