Add secret management guide, client-side performance monitoring, and search accessibility enhancements

- Introduced a comprehensive Secret Management Guide detailing best practices, secret classification, development setup, production management, rotation procedures, and emergency protocols.
- Implemented a client-side performance monitoring script to track various metrics including page load performance, paint metrics, layout shifts, and memory usage.
- Enhanced search accessibility with keyboard navigation support for search results, ensuring compliance with WCAG standards and improving user experience.
This commit is contained in:
pacnpal
2025-12-23 16:41:42 -05:00
parent ae31e889d7
commit edcd8f2076
155 changed files with 22046 additions and 4645 deletions

View File

@@ -0,0 +1,350 @@
# Accessible Component Patterns
This document provides code examples for creating accessible components in ThrillWiki. Follow these patterns to ensure WCAG 2.1 AA compliance.
## Button
### Standard Button
```django
{% include 'components/ui/button.html' with
text='Save Changes'
variant='primary'
type='submit'
%}
```
### Icon Button (REQUIRED: aria_label)
```django
{% include 'components/ui/button.html' with
icon=close_svg
size='icon'
aria_label='Close dialog'
variant='ghost'
%}
```
**Important**: Icon-only buttons MUST include `aria_label` for screen reader accessibility.
### Disabled Button
```django
{% include 'components/ui/button.html' with
text='Submit'
disabled=True
%}
```
### Button with HTMX
```django
{% include 'components/ui/button.html' with
text='Load More'
hx_get='/api/items?page=2'
hx_target='#item-list'
hx_swap='beforeend'
%}
```
## Form Field
### Standard Field
```django
{% include 'forms/partials/form_field.html' with
field=form.email
%}
```
The form_field.html component automatically handles:
- Label association via `for` attribute
- Error message display with proper ARIA
- Required field indication
- Help text with `aria-describedby`
### Field with Help Text
```django
{% include 'forms/partials/form_field.html' with
field=form.password
help_text='Must be at least 8 characters'
%}
```
### Field with HTMX Validation
```django
{% include 'forms/partials/form_field.html' with
field=form.username
hx_validate=True
hx_validate_url='/api/validate-username/'
%}
```
### Complete Form Example
```django
<form method="post" role="form" aria-label="User registration form">
{% csrf_token %}
<fieldset>
<legend class="sr-only">Account Information</legend>
{% include 'forms/partials/form_field.html' with field=form.username %}
{% include 'forms/partials/form_field.html' with field=form.email %}
</fieldset>
<fieldset>
<legend class="sr-only">Security</legend>
{% include 'forms/partials/form_field.html' with field=form.password1 %}
{% include 'forms/partials/form_field.html' with field=form.password2 %}
</fieldset>
{% include 'components/ui/button.html' with
text='Create Account'
type='submit'
variant='primary'
%}
</form>
```
## Modal
### Basic Modal
```django
{% extends 'components/modals/modal_base.html' %}
{% block modal_body %}
<p>Are you sure you want to delete this item?</p>
{% endblock %}
```
### Modal with Actions
```django
{% extends 'components/modals/modal_base.html' %}
{% block modal_body %}
<p>This action cannot be undone.</p>
{% endblock %}
{% block modal_footer %}
<button @click="{{ show_var }} = false" type="button">
Cancel
</button>
{% include 'components/ui/button.html' with
text='Delete'
variant='destructive'
x_on_click='confirmDelete()'
%}
{% endblock %}
```
The modal_inner.html component automatically provides:
- `role="dialog"` and `aria-modal="true"`
- `aria-labelledby` pointing to title
- `aria-describedby` pointing to body (and subtitle if present)
- Focus trap with Tab/Shift+Tab cycling
- Home/End key support for first/last focusable element
- Escape key to close (configurable)
- Auto-focus on first focusable element
## Navigation Menu
### Dropdown Menu
```django
<div x-data="{ open: false }" @click.outside="open = false" @keydown.escape="open = false">
<button
@click="open = !open"
aria-haspopup="true"
:aria-expanded="open.toString()"
aria-label="User menu">
<span>Menu</span>
<i class="fas fa-chevron-down" aria-hidden="true"></i>
</button>
<div
x-show="open"
x-transition
role="menu"
aria-label="User account options"
class="dropdown-menu">
<a role="menuitem" href="/profile" class="menu-item">Profile</a>
<a role="menuitem" href="/settings" class="menu-item">Settings</a>
<div role="separator" class="border-t"></div>
<button role="menuitem" type="submit" class="menu-item">Logout</button>
</div>
</div>
```
## Search
### Accessible Search with Results
```django
<div role="search">
<label for="search-input" class="sr-only">Search parks and rides</label>
<input
id="search-input"
type="search"
placeholder="Search..."
hx-get="/search"
hx-target="#search-results"
hx-trigger="input changed delay:300ms"
autocomplete="off"
aria-describedby="search-status"
aria-controls="search-results"
/>
<div
id="search-results"
role="listbox"
aria-label="Search results"
aria-live="polite">
<!-- Results populated by HTMX -->
</div>
<div id="search-status" class="sr-only" aria-live="polite" aria-atomic="true">
<!-- Status announcements -->
</div>
</div>
```
### Search Result Item
```django
<div role="option" aria-selected="false" class="search-result">
<a href="{{ result.url }}">
<span>{{ result.name }}</span>
<span class="text-muted-foreground">{{ result.type }}</span>
</a>
</div>
```
## Live Regions
### Status Announcements
```django
<div role="status" aria-live="polite" aria-atomic="true" class="sr-only">
{{ status_message }}
</div>
```
Use `aria-live="polite"` for non-urgent updates that can wait for user to finish current action.
### Alert Messages
```django
<div role="alert" aria-live="assertive">
<i class="fas fa-exclamation-circle" aria-hidden="true"></i>
{{ error_message }}
</div>
```
Use `aria-live="assertive"` for critical errors that require immediate attention.
### Loading State
```django
<div role="status" aria-live="polite" aria-busy="true">
<span class="sr-only">Loading results...</span>
<div class="htmx-indicator" aria-hidden="true">
<i class="fas fa-spinner fa-spin"></i>
</div>
</div>
```
## Images
### Meaningful Image
```django
<img
src="{{ park.image.url }}"
alt="{{ park.name }} - {{ park.location }}"
/>
```
### Decorative Image
```django
<img src="/decorative-pattern.svg" alt="" role="presentation" />
```
### Avatar with Name
```django
{% if user.profile.avatar %}
<img
src="{{ user.profile.avatar.url }}"
alt="{{ user.get_full_name|default:user.username }}'s profile picture"
class="avatar"
/>
{% else %}
<div class="avatar-placeholder" aria-hidden="true">
{{ user.username.0|upper }}
</div>
{% endif %}
```
## Breadcrumbs
```django
{% include 'components/navigation/breadcrumbs.html' %}
```
The breadcrumbs component automatically provides:
- `<nav>` element with `aria-label="Breadcrumb"`
- Ordered list for semantic structure
- `aria-current="page"` on current page
- Hidden separators with `aria-hidden="true"`
- Schema.org JSON-LD structured data
## Skip Link
Add to the top of your base template:
```django
<a href="#main-content" class="skip-link sr-only-focusable">
Skip to main content
</a>
<!-- ... header and navigation ... -->
<main id="main-content" tabindex="-1">
{% block content %}{% endblock %}
</main>
```
## Theme Toggle
```django
<button
@click="toggleTheme()"
aria-label="Toggle theme"
:aria-pressed="isDarkMode.toString()"
class="theme-toggle">
<i class="fas fa-sun" aria-hidden="true"></i>
<i class="fas fa-moon" aria-hidden="true"></i>
</button>
```
## Focus Management Utilities
### Focus Trap (Alpine.js)
```javascript
// Already implemented in modal_inner.html
@keydown.tab.prevent="trapFocus($event)"
```
### Return Focus After Action
```javascript
// Store the trigger element
const trigger = document.activeElement;
// After modal closes
trigger.focus();
```
## Testing Your Components
### Quick Accessibility Audit
1. Can you Tab through all interactive elements?
2. Is focus indicator visible?
3. Does Enter/Space activate buttons?
4. Does Escape close modals/dropdowns?
5. Does screen reader announce all content?
6. Is color not the only indicator of state?
### Automated Testing
```bash
# Run accessibility tests
python manage.py test backend.tests.accessibility
# Use axe browser extension for quick audits
# Install from: https://www.deque.com/axe/
```

View File

@@ -0,0 +1,138 @@
# Keyboard Navigation Guide
ThrillWiki is designed to be fully accessible via keyboard. This guide documents all keyboard shortcuts and navigation patterns.
## Global Shortcuts
| Key | Action |
|-----|--------|
| Tab | Move focus forward |
| Shift + Tab | Move focus backward |
| Enter | Activate focused element |
| Space | Activate buttons/checkboxes |
| Escape | Close modals/dropdowns |
## Navigation Menu
| Key | Action |
|-----|--------|
| Tab | Focus menu button |
| Enter/Space | Open menu |
| Arrow Down | Next menu item |
| Arrow Up | Previous menu item |
| Escape | Close menu |
| Home | First menu item |
| End | Last menu item |
## Search
| Key | Action |
|-----|--------|
| Tab | Focus search input |
| Type | Start searching (debounced 300ms) |
| Arrow Down | Navigate to next result |
| Arrow Up | Navigate to previous result (or back to input) |
| Enter | Select current result (navigate to link) |
| Escape | Close results and blur input |
| Home | Jump to first result |
| End | Jump to last result |
## Modals
| Key | Action |
|-----|--------|
| Tab | Next focusable element (trapped within modal) |
| Shift + Tab | Previous focusable element (trapped within modal) |
| Escape | Close modal |
| Home | Jump to first focusable element |
| End | Jump to last focusable element |
## Forms
| Key | Action |
|-----|--------|
| Tab | Move to next field |
| Shift + Tab | Move to previous field |
| Enter | Submit form (when focused on submit button) |
| Space | Toggle checkbox/radio button |
| Arrow Down/Up | Navigate select options |
| Escape | Close select dropdown |
## Dropdowns (User Menu, Browse Menu)
| Key | Action |
|-----|--------|
| Enter/Space | Toggle dropdown open/closed |
| Escape | Close dropdown |
| Arrow Down | Navigate to next item |
| Arrow Up | Navigate to previous item |
| Tab | Move through items (or exit dropdown) |
## Mobile Menu
| Key | Action |
|-----|--------|
| Enter/Space | Open mobile menu |
| Escape | Close mobile menu |
| Tab | Navigate through menu items |
## Theme Toggle
| Key | Action |
|-----|--------|
| Enter/Space | Toggle between light and dark mode |
## Implementation Notes
### Focus Management
- All interactive elements must be reachable via keyboard
- Focus order follows logical reading order (top to bottom, left to right)
- Focus is trapped within modals when open
- Focus returns to trigger element when modal/dropdown closes
### Focus Indicators
- All focusable elements have visible focus indicators
- Focus ring: 2px solid primary color with 2px offset
- Enhanced visibility in high contrast mode
- Custom focus styles for complex components
### ARIA Attributes
- `aria-expanded`: Indicates dropdown/menu open state
- `aria-haspopup`: Indicates element triggers a popup
- `aria-label`: Provides accessible name for icon-only buttons
- `aria-modal`: Indicates modal dialog
- `aria-controls`: Links trigger to controlled element
- `aria-describedby`: Links element to description
- `aria-live`: Announces dynamic content changes
### Skip Links
- Skip to main content link available (hidden until focused)
- Appears at top of page when Tab is pressed first
## Testing Keyboard Navigation
### Manual Testing Checklist
1. Navigate entire site using only keyboard
2. Verify all interactive elements are reachable with Tab
3. Verify Enter/Space activates all buttons and links
4. Verify Escape closes all modals and dropdowns
5. Verify focus indicators are visible
6. Verify focus order is logical
7. Verify focus is trapped in modals
8. Verify focus returns to trigger after closing modals
### Automated Testing
Run accessibility tests:
```bash
python manage.py test backend.tests.accessibility
```
## Browser Compatibility
Keyboard navigation is tested on:
- Chrome (latest)
- Firefox (latest)
- Safari (latest)
- Edge (latest)
Note: Some keyboard shortcuts may conflict with browser shortcuts. In those cases, browser shortcuts take precedence.

View File

@@ -0,0 +1,200 @@
# Screen Reader Testing Checklist
This document provides a comprehensive checklist for testing ThrillWiki with screen readers to ensure WCAG 2.1 AA compliance.
## Recommended Screen Readers
| Screen Reader | Platform | Cost | Notes |
|--------------|----------|------|-------|
| NVDA | Windows | Free | Most widely used free option |
| JAWS | Windows | Commercial | Industry standard |
| VoiceOver | macOS/iOS | Built-in | Activate with Cmd+F5 |
| TalkBack | Android | Built-in | Enable in Accessibility settings |
| Narrator | Windows | Built-in | Basic testing |
## Test Scenarios
### Navigation
- [ ] Skip to main content link works and is announced
- [ ] All navigation items are announced with their role
- [ ] Current page is indicated (aria-current="page")
- [ ] Dropdown menus announce as "button, collapsed" when closed
- [ ] Dropdown menus announce as "button, expanded" when open
- [ ] Menu items announce with role="menuitem"
- [ ] Breadcrumbs are announced as navigation landmark
### Search
- [ ] Search input is announced with label "Search parks and rides"
- [ ] Search results count is announced when results appear
- [ ] Each result is announced with its content
- [ ] Arrow key navigation announces current selection
- [ ] "No results found" is announced when applicable
### Forms
- [ ] All form fields have associated labels announced
- [ ] Required fields announce "required"
- [ ] Error messages are announced immediately when form validates
- [ ] Success messages are announced after form submission
- [ ] Help text is associated and announced with fields
- [ ] Field types are announced (text input, checkbox, select, etc.)
### Interactive Components
- [ ] Buttons announce their purpose/label
- [ ] Icon-only buttons announce their aria-label
- [ ] Links announce destination or purpose
- [ ] Modals announce their title when opened
- [ ] Modal content is announced as dialog
- [ ] Closing modal announces return to previous context
- [ ] Theme toggle announces current state (pressed/not pressed)
### Dynamic Content
- [ ] Search results are announced via aria-live region
- [ ] Filter changes announce result count
- [ ] Status updates (success, error) are announced
- [ ] Loading states are announced ("Loading...")
- [ ] HTMX content swaps announce via live regions
### Images
- [ ] All meaningful images have descriptive alt text
- [ ] Decorative images are hidden (alt="" or aria-hidden)
- [ ] Complex images have long descriptions available
- [ ] Avatar images include user's name in alt text
### Tables (if applicable)
- [ ] Tables have proper headers (th elements)
- [ ] Table caption or aria-label describes table purpose
- [ ] Data cells associate with headers
### Headings
- [ ] Page has exactly one h1
- [ ] Heading hierarchy is logical (h1 > h2 > h3)
- [ ] No skipped heading levels
- [ ] Headings describe section content
## Testing Commands
### NVDA (Windows)
| Command | Action |
|---------|--------|
| Insert + Down Arrow | Read next item |
| Insert + Up Arrow | Read previous item |
| Insert + Space | Read current item |
| Insert + F7 | Elements list (links, headings, landmarks) |
| H | Next heading |
| Shift + H | Previous heading |
| K | Next link |
| F | Next form field |
| D | Next landmark |
| Insert + Ctrl + N | Read notifications |
### VoiceOver (macOS)
| Command | Action |
|---------|--------|
| VO + Right Arrow | Next item |
| VO + Left Arrow | Previous item |
| VO + U | Rotor (elements list) |
| VO + A | Read all from current position |
| VO + Cmd + H | Next heading |
| VO + Cmd + J | Next form control |
| VO + Cmd + L | Next link |
Note: VO = Control + Option
### VoiceOver (iOS)
| Gesture | Action |
|---------|--------|
| Swipe Right | Next item |
| Swipe Left | Previous item |
| Double Tap | Activate item |
| Two-finger Swipe Up | Read all from current position |
| Rotor (two-finger twist) | Change navigation mode |
### JAWS (Windows)
| Command | Action |
|---------|--------|
| Down Arrow | Next item |
| Up Arrow | Previous item |
| Insert + F5 | Forms list |
| Insert + F6 | Headings list |
| Insert + F7 | Links list |
| H | Next heading |
| F | Next form field |
| T | Next table |
| R | Next region/landmark |
### TalkBack (Android)
| Gesture | Action |
|---------|--------|
| Swipe Right | Next item |
| Swipe Left | Previous item |
| Double Tap | Activate item |
| Swipe Up then Down | Navigation settings |
## Common Issues to Watch For
### Problematic Patterns
1. **Missing labels**: Form fields without associated labels
2. **Duplicate IDs**: Multiple elements with same ID breaks aria-describedby
3. **Empty buttons**: Buttons with no text or aria-label
4. **Inaccessible modals**: Focus not trapped, no escape to close
5. **Auto-playing media**: Audio/video that plays automatically
6. **Timeout without warning**: Sessions expiring without notice
7. **Moving focus unexpectedly**: Focus jumping after interactions
8. **Color-only information**: Status conveyed only by color
### Good Patterns
1. **Clear labels**: Every form field has descriptive label
2. **Error prevention**: Clear instructions, validation before submit
3. **Focus management**: Logical order, visible indicators, trapped in modals
4. **Consistent navigation**: Same navigation pattern on all pages
5. **Multiple ways**: Multiple paths to same content
6. **Descriptive links**: Link text describes destination (not "click here")
## Reporting Issues
When reporting accessibility issues, include:
1. Screen reader and version used
2. Browser and version
3. Page URL
4. Steps to reproduce
5. Expected behavior
6. Actual behavior (what was announced)
7. WCAG success criterion violated
## WCAG 2.1 AA Quick Reference
### Level A (Must Have)
- 1.1.1 Non-text Content (alt text)
- 1.3.1 Info and Relationships (semantic HTML)
- 2.1.1 Keyboard (all functionality via keyboard)
- 2.4.1 Bypass Blocks (skip links)
- 4.1.2 Name, Role, Value (ARIA)
### Level AA (Should Have)
- 1.4.3 Contrast (Minimum) (4.5:1 ratio)
- 1.4.4 Resize Text (200% zoom)
- 2.4.6 Headings and Labels (descriptive)
- 2.4.7 Focus Visible (visible focus indicator)
- 3.2.3 Consistent Navigation
- 3.2.4 Consistent Identification
## Resources
- [WCAG 2.1 Guidelines](https://www.w3.org/TR/WCAG21/)
- [WebAIM Screen Reader Survey](https://webaim.org/projects/screenreadersurvey9/)
- [NVDA User Guide](https://www.nvaccess.org/files/nvda/documentation/userGuide.html)
- [VoiceOver User Guide](https://support.apple.com/guide/voiceover/welcome/mac)
- [JAWS Quick Start](https://www.freedomscientific.com/products/software/jaws/)