docs: add WCAG 2.2 AAA accessibility remediation design
Covers 147 issues across 8 sections: CSS foundation, modal system, keyboard access, ARIA live regions, semantic structure, DOCX output, template contrast validation, and miscellaneous fixes.
This commit is contained in:
26
.gitignore
vendored
26
.gitignore
vendored
@@ -1,26 +0,0 @@
|
|||||||
# Logs
|
|
||||||
logs
|
|
||||||
*.log
|
|
||||||
npm-debug.log*
|
|
||||||
yarn-debug.log*
|
|
||||||
yarn-error.log*
|
|
||||||
pnpm-debug.log*
|
|
||||||
lerna-debug.log*
|
|
||||||
|
|
||||||
node_modules
|
|
||||||
dist
|
|
||||||
dist-ssr
|
|
||||||
*.local
|
|
||||||
|
|
||||||
# Editor directories and files
|
|
||||||
.vscode/*
|
|
||||||
!.vscode/extensions.json
|
|
||||||
.idea
|
|
||||||
.DS_Store
|
|
||||||
*.suo
|
|
||||||
*.ntvs*
|
|
||||||
*.njsproj
|
|
||||||
*.sln
|
|
||||||
*.sw?
|
|
||||||
|
|
||||||
_TRASH/
|
|
||||||
163
AGENTS.md
163
AGENTS.md
@@ -1,163 +0,0 @@
|
|||||||
# TypoGenie - AI Coding Agent Guide
|
|
||||||
|
|
||||||
## Project Overview
|
|
||||||
|
|
||||||
TypoGenie is a web application that transforms Markdown content into professionally formatted Microsoft Word documents. Users upload `.md` or `.txt` files, select from 40+ typography styles across multiple categories (Minimalist, Corporate, Editorial, Tech, Creative, etc.), and download a formatted `.docx` file.
|
|
||||||
|
|
||||||
The application uses a local Markdown parser (marked) rather than AI for document conversion, though it was originally designed with AI integration in mind.
|
|
||||||
|
|
||||||
## Technology Stack
|
|
||||||
|
|
||||||
- **Framework**: React 19.2.4 with TypeScript 5.8.2
|
|
||||||
- **Build Tool**: Vite 6.2.0
|
|
||||||
- **Styling**: Tailwind CSS (loaded via CDN in `index.html`)
|
|
||||||
- **Icons**: Lucide React
|
|
||||||
- **Document Generation**: docx library (client-side DOCX creation)
|
|
||||||
- **Markdown Parsing**: marked library (local conversion, no API calls)
|
|
||||||
- **Fonts**: Google Fonts loaded dynamically
|
|
||||||
|
|
||||||
## Project Structure
|
|
||||||
|
|
||||||
```
|
|
||||||
├── App.tsx # Main application component with state machine
|
|
||||||
├── index.tsx # React root mount
|
|
||||||
├── index.html # HTML entry, Tailwind CDN, import maps
|
|
||||||
├── types.ts # TypeScript interfaces (StyleOption, DocxStyleConfig, etc.)
|
|
||||||
├── constants.ts # System prompts and style exports
|
|
||||||
├── components/
|
|
||||||
│ ├── FileUpload.tsx # Drag-and-drop file upload (.md, .txt)
|
|
||||||
│ ├── StyleSelector.tsx # Style grid with preview iframe
|
|
||||||
│ ├── Preview.tsx # Final preview and DOCX export
|
|
||||||
│ └── StylePreviewModal.tsx # (Unused) Modal style preview component
|
|
||||||
|
|
||||||
├── styles/ # Typography style definitions
|
|
||||||
│ ├── index.ts # Aggregates all style categories
|
|
||||||
│ ├── minimalist.ts # 11 minimalist styles
|
|
||||||
│ ├── corporate.ts # 13 corporate/professional styles
|
|
||||||
│ ├── editorial.ts # Editorial/magazine styles
|
|
||||||
│ ├── tech.ts # Tech/startup styles
|
|
||||||
│ ├── creative.ts # Creative/artistic styles
|
|
||||||
│ ├── vintage.ts # Retro/vintage styles
|
|
||||||
│ ├── lifestyle.ts # Lifestyle/blog styles
|
|
||||||
│ ├── academic.ts # Academic/research styles
|
|
||||||
│ └── industrial.ts # Industrial/technical styles
|
|
||||||
```
|
|
||||||
|
|
||||||
## Application State Flow
|
|
||||||
|
|
||||||
The app uses a 4-state machine defined in `types.ts`:
|
|
||||||
|
|
||||||
1. **UPLOAD** - Initial screen with file upload dropzone
|
|
||||||
2. **CONFIG** - Style selection interface with live preview
|
|
||||||
3. **GENERATING** - Processing animation (800ms artificial delay for UX)
|
|
||||||
4. **PREVIEW** - Final preview with DOCX download option
|
|
||||||
|
|
||||||
State transitions:
|
|
||||||
- UPLOAD → CONFIG: After file loaded successfully
|
|
||||||
- CONFIG → GENERATING: When user clicks "Apply Style & Convert"
|
|
||||||
- GENERATING → PREVIEW: After HTML generation completes
|
|
||||||
- PREVIEW → CONFIG: Via "Back to Editor" button
|
|
||||||
- Any → UPLOAD: Via header logo click (reset)
|
|
||||||
|
|
||||||
## Style System Architecture
|
|
||||||
|
|
||||||
Each style is a `StyleOption` object containing:
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
{
|
|
||||||
id: string, // Unique identifier
|
|
||||||
name: string, // Display name
|
|
||||||
category: string, // For filtering (Minimalist, Corporate, etc.)
|
|
||||||
description: string, // Short description
|
|
||||||
vibe: string, // Mood/aesthetic descriptor
|
|
||||||
googleFontsImport: string, // Google Fonts URL
|
|
||||||
wordConfig: {
|
|
||||||
heading1: DocxStyleConfig, // H1 formatting for Word
|
|
||||||
heading2: DocxStyleConfig, // H2 formatting for Word
|
|
||||||
body: DocxStyleConfig, // Body text formatting for Word
|
|
||||||
accentColor: string // Brand accent color
|
|
||||||
},
|
|
||||||
previewCss: string // CSS string for web preview (must manually match wordConfig)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**Important**: `wordConfig` and `previewCss` must be kept in sync manually for visual fidelity between preview and exported document.
|
|
||||||
|
|
||||||
## Build Commands
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Install dependencies
|
|
||||||
npm install
|
|
||||||
|
|
||||||
# Development server (runs on port 3000)
|
|
||||||
npm run dev
|
|
||||||
|
|
||||||
# Production build (outputs to dist/)
|
|
||||||
npm run build
|
|
||||||
|
|
||||||
# Preview production build
|
|
||||||
npm run preview
|
|
||||||
```
|
|
||||||
|
|
||||||
## Environment Configuration
|
|
||||||
|
|
||||||
No environment variables required. The app uses local Markdown parsing only.
|
|
||||||
|
|
||||||
## Key Implementation Details
|
|
||||||
|
|
||||||
### DOCX Generation (components/Preview.tsx)
|
|
||||||
|
|
||||||
The DOCX export uses the `docx` library to create Word-compatible documents:
|
|
||||||
|
|
||||||
- Converts HTML to docx Paragraph elements
|
|
||||||
- Supports headings (h1-h6), paragraphs, blockquotes, lists
|
|
||||||
- Applies style-specific fonts, colors, spacing, borders, and shading
|
|
||||||
- Paper sizes: A4 (210mm × 297mm) and Letter (8.5in × 11in)
|
|
||||||
- Margins: 1in top/bottom, 1.2in left/right
|
|
||||||
|
|
||||||
### Preview System
|
|
||||||
|
|
||||||
Previews render in an iframe with dynamically injected:
|
|
||||||
- Google Fonts link from style config
|
|
||||||
- Style-specific CSS from `previewCss`
|
|
||||||
- Sample content for style selection view
|
|
||||||
- Actual converted content for final preview
|
|
||||||
|
|
||||||
### File Upload
|
|
||||||
|
|
||||||
Accepts: `.md`, `.txt`, `.markdown`
|
|
||||||
Uses FileReader API for client-side text extraction
|
|
||||||
Validates file extension before processing
|
|
||||||
|
|
||||||
## Code Style Guidelines
|
|
||||||
|
|
||||||
- **TypeScript**: Strict mode enabled; all components typed with React.FC
|
|
||||||
- **Imports**: Use `@/` path alias for project root imports
|
|
||||||
- **Styling**: Tailwind utility classes; custom CSS only in `previewCss` fields
|
|
||||||
- **Naming**: PascalCase for components, camelCase for functions/variables
|
|
||||||
- **Comments**: Explain business logic and non-obvious decisions
|
|
||||||
|
|
||||||
## Adding New Typography Styles
|
|
||||||
|
|
||||||
1. Choose appropriate category file in `/styles/` (or create new category)
|
|
||||||
2. Add StyleOption object to the array with:
|
|
||||||
- Unique `id`
|
|
||||||
- Google Fonts URL
|
|
||||||
- Complete `wordConfig` for DOCX export
|
|
||||||
- Matching `previewCss` for web preview
|
|
||||||
3. Export from `/styles/index.ts` if new category
|
|
||||||
4. Test both preview and DOCX export
|
|
||||||
|
|
||||||
## Security Considerations
|
|
||||||
|
|
||||||
- No server-side processing; all conversion happens client-side
|
|
||||||
- No API keys required; all processing is done locally
|
|
||||||
- File upload limited to text files by extension check
|
|
||||||
- iframe sandboxing for style previews (same-origin content only)
|
|
||||||
|
|
||||||
## Known Limitations
|
|
||||||
|
|
||||||
- `wordConfig` and `previewCss` require manual synchronization
|
|
||||||
- DOCX export has limited table support
|
|
||||||
- Complex Markdown (nested lists, code blocks) may not format perfectly
|
|
||||||
- Style categories are loosely organized and may overlap
|
|
||||||
Binary file not shown.
212
docs/plans/2026-02-18-wcag-aaa-accessibility-design.md
Normal file
212
docs/plans/2026-02-18-wcag-aaa-accessibility-design.md
Normal file
@@ -0,0 +1,212 @@
|
|||||||
|
# WCAG 2.2 AAA Accessibility Remediation Design
|
||||||
|
|
||||||
|
**Date:** 2026-02-18
|
||||||
|
**Status:** Approved
|
||||||
|
**Scope:** 147 accessibility issues across 17 existing files, 3 new files
|
||||||
|
|
||||||
|
## Decisions
|
||||||
|
|
||||||
|
| Decision | Choice |
|
||||||
|
|----------|--------|
|
||||||
|
| Visual approach | Balance accessibility with dark aesthetic |
|
||||||
|
| Modals | Native `<dialog>` element |
|
||||||
|
| Style cards | Listbox pattern with existing `useFocusableList` hook |
|
||||||
|
| Template colors | Runtime contrast validation only, no JSON template edits |
|
||||||
|
| Justified text | Override to `left` at render time |
|
||||||
|
| Contrast enforcement | Auto-correct via `ensureContrast()` utility at render time |
|
||||||
|
|
||||||
|
## Section 1: CSS Foundation (~25 issues)
|
||||||
|
|
||||||
|
### Root font size
|
||||||
|
Change `:root { font-size: 16px }` to `font-size: 100%` in `index.css` so user browser preferences are respected.
|
||||||
|
|
||||||
|
### Overflow
|
||||||
|
Remove `overflow: hidden` from `body` and `#root`. Replace with `overflow-x: hidden` to prevent horizontal scroll but allow vertical content access when zoomed to 200%.
|
||||||
|
|
||||||
|
### Focus indicators
|
||||||
|
Replace `rgba(99, 102, 241, 0.3)` focus ring with `rgba(99, 102, 241, 0.8)` at 2px. Meets 3:1 contrast on dark backgrounds while fitting indigo theme.
|
||||||
|
|
||||||
|
### Reduced motion
|
||||||
|
Add global CSS rule:
|
||||||
|
```css
|
||||||
|
@media (prefers-reduced-motion: reduce) {
|
||||||
|
*, *::before, *::after {
|
||||||
|
animation-duration: 0.01ms !important;
|
||||||
|
animation-iteration-count: 1 !important;
|
||||||
|
transition-duration: 0.01ms !important;
|
||||||
|
scroll-behavior: auto !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Forced colors
|
||||||
|
Add `@media (forced-colors: active)` rules for Windows High Contrast Mode compatibility.
|
||||||
|
|
||||||
|
### Color replacements
|
||||||
|
Across all components: `text-zinc-600` -> `text-zinc-400`, `text-zinc-500` -> `text-zinc-400`. All text hits 7:1+ contrast on zinc-950. Keeps dark aesthetic.
|
||||||
|
|
||||||
|
## Section 2: Modal System (~20 issues)
|
||||||
|
|
||||||
|
### New `src/hooks/useDialog.ts`
|
||||||
|
Reusable hook wrapping native `<dialog>`:
|
||||||
|
- Manages `dialogRef`, `showModal()`/`close()`
|
||||||
|
- Restores focus to trigger element on close
|
||||||
|
- Wires `aria-labelledby`
|
||||||
|
- Framer Motion animations on inner content div
|
||||||
|
|
||||||
|
### KeyboardShortcutsHelp (App.tsx)
|
||||||
|
Convert from `motion.div` overlay to `<dialog>`. Add `aria-labelledby` to heading. Close button gets `aria-label="Close shortcuts"`. Escape/focus trap handled natively.
|
||||||
|
|
||||||
|
### ExportOptionsModal
|
||||||
|
Same `<dialog>` conversion. Radio buttons wrapped in `<fieldset><legend>`. Close button labeled.
|
||||||
|
|
||||||
|
### StylePreviewModal
|
||||||
|
Same `<dialog>` conversion. Close button labeled. Iframe HTML gets `lang="en"`.
|
||||||
|
|
||||||
|
### Animation approach
|
||||||
|
`dialog[open]` CSS selector for entrance. `::backdrop` gets fade-in. Framer Motion on inner content for exit.
|
||||||
|
|
||||||
|
## Section 3: Keyboard Access & Interactive Elements (~20 issues)
|
||||||
|
|
||||||
|
### Logo button
|
||||||
|
Replace `<motion.div onClick>` with `<motion.button aria-label="TypoGenie - Reset to home">`.
|
||||||
|
|
||||||
|
### Single-character shortcut
|
||||||
|
Scope `?`/`/` listener to only fire when `document.activeElement` is body or non-input element. Prevents intercepting typing in search.
|
||||||
|
|
||||||
|
### Style cards as listbox
|
||||||
|
Container: `role="listbox"` + `aria-label="Typography styles"`. Each card: `role="option"` + `tabIndex` + `aria-selected` + `onKeyDown`. Wire `useFocusableList` for arrow keys. Add `:focus-visible` indigo ring.
|
||||||
|
|
||||||
|
### Favorite button
|
||||||
|
Add `aria-label` (dynamic: "Add to favorites"/"Remove from favorites") + `aria-pressed`.
|
||||||
|
|
||||||
|
### Category filters
|
||||||
|
Wrap in `<div role="group" aria-label="Filter by category">`. Active button: `aria-pressed="true"`.
|
||||||
|
|
||||||
|
### Paper size buttons
|
||||||
|
Wrap in `<div role="group" aria-label="Paper size">`. Active button: `aria-pressed="true"`.
|
||||||
|
|
||||||
|
### Search input
|
||||||
|
Add `aria-label="Search templates"` + `aria-describedby` pointing to live result count region.
|
||||||
|
|
||||||
|
### Icon-only buttons
|
||||||
|
Add `aria-label` to all: zoom buttons, external link (Preview), close buttons. Decorative icons next to text: `aria-hidden="true"`.
|
||||||
|
|
||||||
|
### Target sizes
|
||||||
|
Increase padding on small buttons to hit 44x44px minimum. Use `min-w-[44px] min-h-[44px]` with centered content where inline growth not possible.
|
||||||
|
|
||||||
|
## Section 4: ARIA Live Regions & Status Announcements (~15 issues)
|
||||||
|
|
||||||
|
### Root status region
|
||||||
|
Persistent `<div role="status" aria-live="polite" className="sr-only">` in App.tsx root. Updated on `appState` changes.
|
||||||
|
|
||||||
|
### Generating state
|
||||||
|
Add `aria-busy="true"` + `role="status"` to container.
|
||||||
|
|
||||||
|
### Preview loading
|
||||||
|
Wrap "Loading..." in `role="status"`.
|
||||||
|
|
||||||
|
### Success message
|
||||||
|
`aria-live="polite"` on button text area for "Saved!" announcement.
|
||||||
|
|
||||||
|
### Search results
|
||||||
|
Visually-hidden `role="status"` announcing "{N} templates found". Debounced 300ms.
|
||||||
|
|
||||||
|
### Error fixes
|
||||||
|
- Remove `aria-live="polite"` where `role="alert"` already set (implies assertive)
|
||||||
|
- Remove 5-second auto-dismiss from FileUpload error
|
||||||
|
- Add `aria-describedby` linking FileUpload error to dropzone
|
||||||
|
- Template loading: `role="status"`. Template error: `role="alert"`
|
||||||
|
|
||||||
|
### Loading state
|
||||||
|
Replace `return null` with minimal `<div role="status">Loading TypoGenie</div>`.
|
||||||
|
|
||||||
|
## Section 5: Semantic Structure & Landmarks (~15 issues)
|
||||||
|
|
||||||
|
### Skip navigation
|
||||||
|
`<a href="#main-content" class="sr-only focus:not-sr-only">Skip to main content</a>` in index.html.
|
||||||
|
|
||||||
|
### Page title
|
||||||
|
`<title>TypoGenie - Markdown to Word Converter</title>`
|
||||||
|
|
||||||
|
### Noscript
|
||||||
|
`<noscript><p>TypoGenie requires JavaScript to run.</p></noscript>`
|
||||||
|
|
||||||
|
### Landmarks
|
||||||
|
- `id="main-content"` on `<main>` in both app states
|
||||||
|
- `<nav aria-label="Style filters">` around category buttons
|
||||||
|
- `<section aria-label="Style list">` and `<section aria-label="Style preview">` in StyleSelector
|
||||||
|
|
||||||
|
### Progress stepper
|
||||||
|
Wrap in `<nav aria-label="Progress"><ol>`. Active step: `aria-current="step"`.
|
||||||
|
|
||||||
|
### Shortcuts list
|
||||||
|
Convert to `<dl>` with `<dt>`/`<dd>`.
|
||||||
|
|
||||||
|
### Decorative elements
|
||||||
|
`aria-hidden="true"` on blob backgrounds, window-chrome dots, visual dividers.
|
||||||
|
|
||||||
|
### Iframe lang
|
||||||
|
Both Preview.tsx and StylePreviewModal.tsx: inject `<html lang="en">`.
|
||||||
|
|
||||||
|
## Section 6: DOCX Output Accessibility (~8 issues)
|
||||||
|
|
||||||
|
### Document metadata
|
||||||
|
Add `title` (from filename/first h1), `description`, `language` to Document constructor.
|
||||||
|
|
||||||
|
### Image handling
|
||||||
|
New `<img>` handler producing `[Image: {alt text}]` placeholder paragraph in italic.
|
||||||
|
|
||||||
|
### Table headers
|
||||||
|
Set `tableHeader: true` on `TableRow` containing `<th>` elements.
|
||||||
|
|
||||||
|
### Heading preservation in table mode
|
||||||
|
Add `HeadingLevel` to paragraph inside `processHeaderAsTable` table cells.
|
||||||
|
|
||||||
|
### Code block style
|
||||||
|
Add custom "Code" paragraph style to DOCX styles. Apply to code blocks.
|
||||||
|
|
||||||
|
## Section 7: Template Contrast Validation (~20 issues)
|
||||||
|
|
||||||
|
### New `src/utils/contrastUtils.ts`
|
||||||
|
- `hexToRgb(hex)` - parse hex to RGB
|
||||||
|
- `relativeLuminance(rgb)` - WCAG luminance formula
|
||||||
|
- `contrastRatio(color1, color2)` - ratio calculation
|
||||||
|
- `ensureContrast(fg, bg, minRatio)` - auto-correct foreground color until ratio met
|
||||||
|
|
||||||
|
### Integration in templateRenderer.ts
|
||||||
|
Run `ensureContrast()` on every text/background pair at CSS generation time. Body text: 7:1. Large text (18pt+ or 14pt+ bold): 4.5:1.
|
||||||
|
|
||||||
|
### `del` element fix
|
||||||
|
When resolving `del` color, if `border` palette color fails contrast, fall back to `textSecondary`.
|
||||||
|
|
||||||
|
### Justified text
|
||||||
|
Override `justify` to `left` at render time (1.4.8 AAA prohibits justified text).
|
||||||
|
|
||||||
|
### Line-height floor
|
||||||
|
Enforce minimum 1.5 for body text, 1.0 for headings. Clamp upward if template specifies lower.
|
||||||
|
|
||||||
|
## Section 8: Remaining Miscellaneous (~10 issues)
|
||||||
|
|
||||||
|
### Iframe keyboard
|
||||||
|
`tabIndex={-1}` on preview iframe in StyleSelector (passive preview, not interactive).
|
||||||
|
|
||||||
|
### Error boundary
|
||||||
|
New `src/components/ErrorBoundary.tsx` wrapping `<App />`. Renders accessible error with `role="alert"` on crash.
|
||||||
|
|
||||||
|
### Framer Motion reduced motion
|
||||||
|
Import `useReducedMotion` from `motion/react` in App.tsx. When true, disable infinite blob/gradient animations via conditional animate props.
|
||||||
|
|
||||||
|
### Hidden buttons on small screens
|
||||||
|
Add always-visible `<kbd>?</kbd>` hint regardless of breakpoint.
|
||||||
|
|
||||||
|
### Truncation
|
||||||
|
Add `title` attribute with full description on `line-clamp-2` elements.
|
||||||
|
|
||||||
|
## Files Modified (17)
|
||||||
|
|
||||||
|
`index.html`, `src/index.css`, `src/App.tsx`, `src/main.tsx`, `src/components/StyleSelector.tsx`, `src/components/Preview.tsx`, `src/components/ExportOptionsModal.tsx`, `src/components/StylePreviewModal.tsx`, `src/components/FileUpload.tsx`, `src/hooks/useSettings.ts`, `src/hooks/useTemplates.ts`, `src/services/templateRenderer.ts`, `src/utils/docxConverter.ts`, `src/utils/fontUtils.ts`
|
||||||
|
|
||||||
|
## Files Created (3)
|
||||||
|
|
||||||
|
`src/utils/contrastUtils.ts`, `src/hooks/useDialog.ts`, `src/components/ErrorBoundary.tsx`
|
||||||
Reference in New Issue
Block a user