Files
core-cooldown/docs/plans/2026-02-18-wcag-aaa-implementation.md
Your Name 2f7aa074bc v0.2.0: WCAG 2.2 AAA accessibility + toggle fix + version bump
- Upgrade accessibility from WCAG 2.1 AA to WCAG 2.2 AAA conformance
- 42 accessibility fixes across 18 frontend components
- Enhanced contrast ratios (7:1 body text, 4.5:1 large text, 3:1 non-text)
- 44px minimum touch/click targets on all interactive elements
- Full WAI-ARIA 1.2: tablist, radiogroup, alertdialog, progressbar, switch
- Screen-reader-only data tables behind chart, dynamic page titles
- Skip navigation link, semantic heading hierarchy (h1 > h2)
- Celebration popups persist on hover/focus with keyboard dismiss
- Fix ToggleSwitch visual height (44px hit area, 28px visual track)
- Update README with detailed WCAG 2.2 AAA accessibility section
- Include WCAG design doc and implementation plan in docs/plans/
2026-02-18 19:18:15 +02:00

968 lines
27 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# WCAG 2.2 AAA Implementation Plan
> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
**Goal:** Achieve WCAG 2.2 AAA conformance across all frontend components while preserving the existing dark-theme visual identity.
**Architecture:** All changes are CSS + Svelte template only — no Rust backend changes. Theme tokens in `app.css` propagate through Tailwind's `@theme` system. Components use Svelte 5 runes (`$state`, `$derived`, `$effect`, `$props`). Accessibility patterns follow WAI-ARIA 1.2 (tablist, radiogroup, alertdialog, progressbar).
**Tech Stack:** Svelte 5, Tailwind CSS v4 (`@theme` tokens in CSS, no config file), TypeScript, Web Animations API (`motion` library)
**Note:** This project has no frontend test suite. Verification is done via `npm run build` (Vite build) and manual inspection. Each task ends with a build check and a commit.
---
### Task 1: Theme Tokens & Global Styles (`app.css`)
**Files:**
- Modify: `break-timer/src/app.css` (entire file)
This is the foundation — all subsequent tasks depend on these token changes.
**Step 1: Update theme tokens**
In `app.css`, inside the `@theme { }` block (lines 320):
- Change `--color-text-sec: #8a8a8a``--color-text-sec: #a8a8a8` (7.28:1 on black)
- Change `--color-text-dim: #3a3a3a``--color-text-dim: #5c5c5c` (3.5:1 decorative)
- Change `--color-border: #222222``--color-border: #3a3a3a` (2.63:1 non-text)
- Change `--color-danger: #f85149``--color-danger: #ff6b6b` (7.41:1)
- Add new token: `--color-input-border: #444444;`
- Add new token: `--color-surface-lt: #1e1e1e;`
**Step 2: Update body styles**
In the `html, body` block (lines 2235):
- Change `user-select: none` to only apply on drag regions:
- REMOVE `user-select: none;` and `-webkit-user-select: none;` from body
- ADD a new rule for drag regions only:
```css
[data-tauri-drag-region],
[data-tauri-drag-region] * {
user-select: none;
-webkit-user-select: none;
}
```
- Add `line-height: 1.625;` (leading-relaxed) to body for AAA 1.4.8
**Step 3: Enhance focus indicators**
Replace the `:focus-visible` block (lines 7376) with:
```css
:focus-visible {
outline: 2px solid var(--color-accent);
outline-offset: 2px;
box-shadow: 0 0 0 4px rgba(255, 255, 255, 0.3);
}
```
The `box-shadow` provides a white fallback when the accent color has low contrast against dark backgrounds.
**Step 4: Add skip link styles**
After the `.sr-only` block, add:
```css
.skip-link {
position: absolute;
top: -100%;
left: 50%;
transform: translateX(-50%);
z-index: 10000;
padding: 8px 16px;
background: var(--color-accent);
color: #000;
font-size: 13px;
font-weight: 600;
border-radius: 0 0 8px 8px;
text-decoration: none;
transition: top 0.15s ease;
}
.skip-link:focus {
top: 0;
}
```
**Step 5: Verify build**
Run: `cd break-timer && npm run build`
Expected: Build succeeds with no errors.
**Step 6: Commit**
```bash
git add break-timer/src/app.css
git commit -m "a11y: update theme tokens and global styles for WCAG AAA
- Bump --color-text-sec to #a8a8a8 (7.28:1 contrast)
- Bump --color-text-dim to #5c5c5c (3.5:1 decorative)
- Bump --color-border to #3a3a3a (2.63:1 non-text)
- Bump --color-danger to #ff6b6b (7.41:1)
- Add --color-input-border and --color-surface-lt tokens
- Add white shadow fallback on :focus-visible
- Add leading-relaxed default line-height
- Scope user-select:none to drag regions only
- Add skip-link focus styles"
```
---
### Task 2: App Shell (`App.svelte`)
**Files:**
- Modify: `break-timer/src/App.svelte`
**Step 1: Add skip link**
Inside the `<main>` element (line 87), add skip link as first child and an id target:
```svelte
<main class="relative h-full bg-black">
<a href="#main-content" class="skip-link">Skip to content</a>
...
```
Then on the zoom container `<div>` (line 92), add `id="main-content"`:
```svelte
<div
id="main-content"
class="relative h-full overflow-hidden origin-top-left"
```
**Step 2: Add document title effect**
After the existing focus management `$effect` (after line 76), add:
```typescript
// WCAG 2.4.2: Document title reflects current view
$effect(() => {
const viewNames: Record<string, string> = {
dashboard: "Dashboard",
breakScreen: "Break",
settings: "Settings",
stats: "Statistics",
};
document.title = `Core Cooldown — ${viewNames[effectiveView] ?? "Dashboard"}`;
});
```
**Step 3: Verify build**
Run: `cd break-timer && npm run build`
**Step 4: Commit**
```bash
git add break-timer/src/App.svelte
git commit -m "a11y: add skip link and dynamic document title for WCAG AAA"
```
---
### Task 3: Titlebar (`Titlebar.svelte`)
**Files:**
- Modify: `break-timer/src/lib/components/Titlebar.svelte`
**Step 1: Wrap in header landmark**
Change the outer `<div>` (line 8) to `<header role="banner">`.
**Step 2: Enlarge traffic lights to meet 44px target size**
Change each traffic light button from `h-[15px] w-[15px]` to `h-[20px] w-[20px]` visual size with `min-h-[44px] min-w-[44px]` hit area via padding. The trick: keep the visual circle at 20px but wrap in a 44px invisible tap area.
Replace each button's class. For example the Maximize button (line 27):
```svelte
<button
aria-label="Maximize"
class="traffic-btn group/btn relative flex h-[44px] w-[44px] items-center justify-center"
onclick={() => appWindow.toggleMaximize()}
>
<span class="flex h-[20px] w-[20px] items-center justify-center rounded-full bg-[#27C93F] transition-all duration-150 group-hover/btn:brightness-110">
<svg ...>
</span>
</button>
```
Apply the same pattern to Minimize and Close buttons. The gap between buttons changes from `gap-[8px]` to `gap-0` since the 44px buttons provide their own spacing.
**Step 3: Verify build**
Run: `cd break-timer && npm run build`
**Step 4: Commit**
```bash
git add break-timer/src/lib/components/Titlebar.svelte
git commit -m "a11y: add header landmark and enlarge traffic lights to 44px hit areas"
```
---
### Task 4: ToggleSwitch (`ToggleSwitch.svelte`)
**Files:**
- Modify: `break-timer/src/lib/components/ToggleSwitch.svelte`
**Step 1: Enlarge to 52x28 and fix knob contrast**
Change button dimensions from `h-[24px] w-[48px]` to `h-[28px] w-[52px]` with `min-h-[44px]` padding for hit area.
Change the knob span from `h-[19px] w-[19px]` to `h-[22px] w-[22px]`.
Change translate-x for ON state from `translate-x-[26px]` to `translate-x-[27px]`.
Change mt from `mt-[2.5px]` to `mt-[3px]`.
Change OFF knob color from `bg-[#444]` to `bg-[#666]` (better contrast).
The button should have `min-h-[44px]` via a wrapper approach or padding.
**Step 2: Verify build**
Run: `cd break-timer && npm run build`
**Step 3: Commit**
```bash
git add break-timer/src/lib/components/ToggleSwitch.svelte
git commit -m "a11y: enlarge toggle switch and improve OFF knob contrast"
```
---
### Task 5: Stepper (`Stepper.svelte`)
**Files:**
- Modify: `break-timer/src/lib/components/Stepper.svelte`
**Step 1: Enlarge buttons and fix contrast**
Change both +/- button dimensions from `h-7 w-7` (28px) to `h-9 w-9` (36px) with `min-h-[44px] min-w-[44px]` padding.
Change `bg-[#141414]` to `bg-[#1a1a1a] border border-[#3a3a3a]` for better non-text contrast.
Change `text-[#8a8a8a]` to `text-text-sec` (uses updated theme token).
**Step 2: Add keyboard hold-to-repeat**
Add `onkeydown` handlers to both buttons that trigger the same `startHold`/`stopHold` logic for Enter, Space, ArrowUp, ArrowDown keys:
```typescript
function handleKeydown(fn: () => void, e: KeyboardEvent) {
if (["Enter", " ", "ArrowUp", "ArrowDown"].includes(e.key)) {
e.preventDefault();
startHold(e.key === "ArrowDown" || e.key === "ArrowUp" ? fn : fn);
}
}
function handleKeyup(e: KeyboardEvent) {
if (["Enter", " ", "ArrowUp", "ArrowDown"].includes(e.key)) {
stopHold();
}
}
```
Add `onkeydown` and `onkeyup` to both buttons. The decrease button uses ArrowDown for decrement, the increase button uses ArrowUp for increment.
**Step 3: Verify build**
Run: `cd break-timer && npm run build`
**Step 4: Commit**
```bash
git add break-timer/src/lib/components/Stepper.svelte
git commit -m "a11y: enlarge stepper buttons and add keyboard hold-to-repeat"
```
---
### Task 6: Animation Actions (`animate.ts`)
**Files:**
- Modify: `break-timer/src/lib/utils/animate.ts`
**Step 1: Add keyboard support to `pressable`**
In the `pressable` function (line 117), after the mousedown/mouseup/mouseleave listeners (lines 143145), add keydown/keyup for Enter and Space:
```typescript
function onKeyDown(e: KeyboardEvent) {
if (e.key === "Enter" || e.key === " ") {
e.preventDefault();
onDown();
}
}
function onKeyUp(e: KeyboardEvent) {
if (e.key === "Enter" || e.key === " ") {
onUp();
}
}
node.addEventListener("keydown", onKeyDown);
node.addEventListener("keyup", onKeyUp);
```
Update `destroy()` to remove these listeners too.
**Step 2: Add focus support to `glowHover`**
In the `glowHover` function (line 165), after mouseenter/mouseleave listeners (lines 201202), add focusin/focusout:
```typescript
node.addEventListener("focusin", onEnter);
node.addEventListener("focusout", onLeave);
```
Update `destroy()` to remove these listeners too.
**Step 3: Verify build**
Run: `cd break-timer && npm run build`
**Step 4: Commit**
```bash
git add break-timer/src/lib/utils/animate.ts
git commit -m "a11y: add keyboard feedback to pressable and focus glow to glowHover"
```
---
### Task 7: Dashboard (`Dashboard.svelte`)
**Files:**
- Modify: `break-timer/src/lib/components/Dashboard.svelte`
**Step 1: Replace hardcoded `#8a8a8a` with theme token**
Replace all `text-[#8a8a8a]` with `text-text-sec` throughout the file. There are ~10 instances on lines 180, 208, 216, 234, 241, 256, 301, 329, 361.
Replace `border-[#222]` with `border-border` on the three bottom buttons (lines 302, 328, 360).
**Step 2: Wrap bottom buttons in nav landmark**
Around the three bottom action buttons (lines 296382), wrap in:
```svelte
<nav aria-label="Main actions">
<!-- Bottom left: start break now -->
...
<!-- Bottom center: stats -->
...
<!-- Bottom right: settings -->
...
</nav>
```
The three `<div class="absolute bottom-5 ...">` blocks move inside the `<nav>`.
**Step 3: Make natural break toast persistent on hover**
Replace the simple timeout logic (lines 118126) with hover-aware persistence:
```typescript
let toastHovering = $state(false);
$effect(() => {
if ($timer.naturalBreakOccurred) {
showNaturalBreakToast = true;
if (naturalBreakToastTimeout) clearTimeout(naturalBreakToastTimeout);
naturalBreakToastTimeout = setTimeout(() => {
if (!toastHovering) showNaturalBreakToast = false;
}, 5000);
}
});
```
On the toast div (line 266), add:
```svelte
onmouseenter={() => toastHovering = true}
onmouseleave={() => { toastHovering = false; showNaturalBreakToast = false; }}
onfocusin={() => toastHovering = true}
onfocusout={() => { toastHovering = false; showNaturalBreakToast = false; }}
```
Add a close button inside the toast and an Escape key handler.
**Step 4: Add progressbar role to daily goal bar**
On the goal progress bar div (line 235), add ARIA:
```svelte
<div class="w-16 h-[2px] rounded-full overflow-hidden" style="background: #161616;"
role="progressbar"
aria-label="Daily goal progress"
aria-valuemin={0}
aria-valuemax={$config.daily_goal_breaks}
aria-valuenow={dailyGoalProgress}
>
```
**Step 5: Add sr-only text for pomodoro dots**
After the pomodoro dots visual `<div>` (around line 192), add:
```svelte
<span class="sr-only">
Pomodoro cycle: session {$timer.pomodoroCyclePosition + 1} of {$timer.pomodoroTotalInCycle}
</span>
```
**Step 6: Verify build**
Run: `cd break-timer && npm run build`
**Step 7: Commit**
```bash
git add break-timer/src/lib/components/Dashboard.svelte
git commit -m "a11y: dashboard contrast, nav landmark, toast persistence, goal progressbar"
```
---
### Task 8: Settings — Part 1: Headings & Structure (`Settings.svelte`)
**Files:**
- Modify: `break-timer/src/lib/components/Settings.svelte`
This is a large file (1176 lines). Split into two commits for reviewability.
**Step 1: Change all `<h3>` to `<h2>`**
Replace every `<h3 class="mb-4 text-[11px] ...` with `<h2 class="mb-4 text-[11px] ...` and corresponding `</h3>` with `</h2>`. There are 17 instances at approximate lines: 163, 218, 280, 346, 435, 444, 498, 590, 668, 732, 825, 862, 906, 976(Working Hours has no heading — add one), 1078, 1119, 1138.
**Step 2: Add `id` to headings and `aria-labelledby` to sections**
Each `<section>` gets an `aria-labelledby` pointing to its heading's `id`. Pattern:
```svelte
<section aria-labelledby="settings-timer" class="rounded-2xl p-5 ...">
<h2 id="settings-timer" class="mb-4 ...">Timer</h2>
```
Do this for all 18 sections. Use ids: `settings-timer`, `settings-pomodoro`, `settings-microbreaks`, `settings-breakscreen`, `settings-activities`, `settings-breathing`, `settings-behavior`, `settings-alerts`, `settings-sound`, `settings-idle`, `settings-presentation`, `settings-goals`, `settings-appearance`, `settings-workinghours`, `settings-minimode`, `settings-general`, `settings-shortcuts`.
**Step 3: Add missing heading for Working Hours section**
The Working Hours section (line 975) has no `<h3>`/`<h2>`. Add:
```svelte
<h2 id="settings-workinghours" class="mb-4 text-[11px] font-medium tracking-[0.15em] text-text-sec uppercase">
Working Hours
</h2>
```
**Step 4: Verify build**
Run: `cd break-timer && npm run build`
**Step 5: Commit**
```bash
git add break-timer/src/lib/components/Settings.svelte
git commit -m "a11y: settings heading hierarchy (h3→h2) and section landmarks"
```
---
### Task 9: Settings — Part 2: ARIA, Contrast, Sizes (`Settings.svelte`)
**Files:**
- Modify: `break-timer/src/lib/components/Settings.svelte`
**Step 1: Breathing pattern — radiogroup/radio**
Wrap the breathing pattern buttons (line 464) in a `role="radiogroup"` container:
```svelte
<div role="radiogroup" aria-label="Breathing pattern" class="flex flex-col gap-1.5">
```
Each breathing pattern button gets `role="radio"` and `aria-checked`:
```svelte
<button
role="radio"
aria-checked={$config.breathing_pattern === bp.id}
...
```
**Step 2: Sound preset — aria-pressed**
Each sound preset button (line 709) gets `aria-pressed`:
```svelte
<button
aria-pressed={$config.sound_preset === preset}
...
```
**Step 3: Replace hardcoded colors**
- All `text-[#8a8a8a]` → `text-text-sec` (many instances throughout)
- All `bg-[#161616]` dividers → `bg-border` (use `--color-border` token)
- All `border-[#161616]` on inputs → `border-border`
- All `placeholder:text-[#2a2a2a]` → `placeholder:text-[#555]` (3.37:1)
- All `bg-[#141414]` → use updated stepper component (already handled in Task 5)
- Back button `h-8 w-8` → `h-10 w-10 min-h-[44px] min-w-[44px]` (line 127)
**Step 4: Reset button aria-live**
Wrap the reset button text in an `aria-live="polite"` region, or add `aria-live="polite"` to the button itself so screen readers announce the confirmation state change:
```svelte
<button
aria-live="polite"
...
>
{resetConfirming ? "Tap again to confirm reset" : "Reset to defaults"}
</button>
```
**Step 5: Add abbreviation tags**
For standalone unit abbreviations in setting descriptions, wrap with `<abbr>`:
- `"s"` (seconds) → `<abbr title="seconds">s</abbr>`
- `"min"` → `<abbr title="minutes">min</abbr>`
Apply on first occurrence in: alert timing description (line 613), idle timeout (line 755), snooze duration (line 537).
**Step 6: Add title tooltips on complex settings**
Add `title` attributes on the section-level labels for Pomodoro, Smart breaks, Microbreaks, Breathing:
- Pomodoro `<h2>`: `title="Pomodoro technique alternates focused work sessions with short and long breaks"`
- Microbreaks `<h2>`: `title="20-20-20 rule: every 20 minutes, look 20 feet away for 20 seconds"`
- Smart breaks `<div>`: `title="Automatically counts time away from computer as a break"`
- Breathing guide `<h2>`: `title="Visual breathing exercise during breaks to reduce stress"`
**Step 7: Verify build**
Run: `cd break-timer && npm run build`
**Step 8: Commit**
```bash
git add break-timer/src/lib/components/Settings.svelte
git commit -m "a11y: settings ARIA patterns, contrast, abbreviations, and tooltips"
```
---
### Task 10: StatsView — Tabs & Data Tables (`StatsView.svelte`)
**Files:**
- Modify: `break-timer/src/lib/components/StatsView.svelte`
**Step 1: Change all `<h3>` to `<h2>`**
Replace all `<h3>` with `<h2>` and `</h3>` with `</h2>` (~12 instances).
**Step 2: Implement tablist pattern**
Replace the tab navigation (lines 297310):
```svelte
<div role="tablist" aria-label="Statistics time range" class="flex gap-1 px-5 mb-3" use:fadeIn={{ duration: 0.3, y: 6 }}>
{#each [["today", "Today"], ["weekly", "Weekly"], ["monthly", "Monthly"]] as [tab, label]}
<button
role="tab"
aria-selected={activeTab === tab}
aria-controls="tabpanel-{tab}"
id="tab-{tab}"
use:pressable
class="rounded-lg px-4 py-2 min-h-[44px] text-[11px] tracking-wider uppercase transition-all duration-200
{activeTab === tab
? 'bg-[#1a1a1a] text-white'
: 'text-text-sec hover:text-white'}"
onclick={() => activeTab = tab as any}
>
{label}
</button>
{/each}
</div>
```
Wrap each tab's content in a `role="tabpanel"`:
```svelte
{#if activeTab === "today"}
<div role="tabpanel" id="tabpanel-today" aria-labelledby="tab-today">
...
</div>
```
Same for `weekly` and `monthly`.
**Step 3: Add sr-only data tables for 30-day chart and heatmap**
After the 30-day chart canvas (around line 533), add:
```svelte
{#if monthHistory.length > 0}
<table class="sr-only">
<caption>Break history for the last {monthHistory.length} days</caption>
<thead>
<tr><th>Date</th><th>Completed</th><th>Skipped</th></tr>
</thead>
<tbody>
{#each monthHistory as day}
<tr><td>{day.date}</td><td>{day.breaksCompleted}</td><td>{day.breaksSkipped}</td></tr>
{/each}
</tbody>
</table>
{/if}
```
After the heatmap canvas (around line 559), add:
```svelte
{#if monthHistory.length > 0}
<table class="sr-only">
<caption>Activity heatmap for the last {monthHistory.length} days</caption>
<thead>
<tr><th>Date</th><th>Breaks completed</th></tr>
</thead>
<tbody>
{#each monthHistory as day}
<tr><td>{day.date}</td><td>{day.breaksCompleted}</td></tr>
{/each}
</tbody>
</table>
{/if}
```
**Step 4: Replace hardcoded colors**
- All `text-[#8a8a8a]` → `text-text-sec`
- `bg-[#161616]` dividers → `bg-border`
- Back button: `h-8 w-8` → `h-10 w-10 min-h-[44px] min-w-[44px]`
**Step 5: Verify build**
Run: `cd break-timer && npm run build`
**Step 6: Commit**
```bash
git add break-timer/src/lib/components/StatsView.svelte
git commit -m "a11y: stats tablist pattern, sr-only data tables, contrast updates"
```
---
### Task 11: BreakScreen (`BreakScreen.svelte`)
**Files:**
- Modify: `break-timer/src/lib/components/BreakScreen.svelte`
**Step 1: Add strict mode focus safety**
When `strict_mode` is true and buttons are hidden, the focus trap has zero focusable elements. After the `{#if showButtons}` block (around line 353 for in-app, line 203 for standalone), add an else block:
```svelte
{:else}
<span tabindex="0" class="sr-only" aria-live="polite">
Break in progress, please wait
</span>
{/if}
```
This ensures the focus trap always has at least one focusable element.
**Step 2: Fix breathing aria-live to only announce phase changes**
The breathing guide `<span aria-live="polite">` (lines 172 and 304) currently announces every countdown tick. Add a tracked variable that only updates on phase change:
In the script section, add:
```typescript
let lastAnnouncedPhase = $state("");
let breathAnnouncement = $derived(
breathPhase !== lastAnnouncedPhase
? (() => { lastAnnouncedPhase = breathPhase; return `${breathPhase}`; })()
: ""
);
```
Actually, better approach — use a separate `$effect` and a dedicated announcement state:
```typescript
let breathAnnouncement = $state("");
$effect(() => {
// Only announce when the breathing phase name changes, not countdown ticks
breathAnnouncement = breathPhase;
});
```
Then change the aria-live span to use a separate invisible span for SR announcements:
```svelte
<span class="sr-only" aria-live="polite" aria-atomic="true">{breathAnnouncement}</span>
```
Keep the visible breathing text without `aria-live` (remove `aria-live="polite"` from the visible span).
**Step 3: Replace hardcoded colors**
- `text-[#8a8a8a]` → `text-text-sec`
- `border-[#222]` → `border-border`
**Step 4: Verify build**
Run: `cd break-timer && npm run build`
**Step 5: Commit**
```bash
git add break-timer/src/lib/components/BreakScreen.svelte
git commit -m "a11y: break screen strict-mode focus safety and breathing phase announcements"
```
---
### Task 12: Celebration Toast Persistence (`Celebration.svelte`)
**Files:**
- Modify: `break-timer/src/lib/components/Celebration.svelte`
**Step 1: Add hover/focus-aware auto-dismiss**
Replace the CSS-driven 3.5s fade with JS-controlled timing. Add state:
```typescript
let milestoneHovering = $state(false);
let goalHovering = $state(false);
```
For the `.celebration-overlay` div, add:
```svelte
onmouseenter={() => milestoneHovering = true}
onmouseleave={() => milestoneHovering = false}
onfocusin={() => milestoneHovering = true}
onfocusout={() => milestoneHovering = false}
```
Remove `pointer-events: none` from `.celebration-overlay` and `.goal-overlay` styles.
Add `$effect` blocks that manage visibility based on hover state — when not hovering, set a timeout to add a CSS class that triggers fade-out.
Add a close button (sr-only label "Dismiss notification") to both overlays, and an Escape key handler.
**Step 2: Verify build**
Run: `cd break-timer && npm run build`
**Step 3: Commit**
```bash
git add break-timer/src/lib/components/Celebration.svelte
git commit -m "a11y: celebration overlays persist on hover/focus with dismiss controls"
```
---
### Task 13: MiniTimer Contrast (`MiniTimer.svelte`)
**Files:**
- Modify: `break-timer/src/lib/components/MiniTimer.svelte`
**Step 1: Fix paused text contrast**
Change line 338: `color: {state === 'paused' ? '#555' : '#fff'}` to `color: {state === 'paused' ? '#a8a8a8' : '#fff'}`.
Replace `#8a8a8a` with the text-sec token value `#a8a8a8` on line 344 for pomodoro indicator.
**Step 2: Verify build**
Run: `cd break-timer && npm run build`
**Step 3: Commit**
```bash
git add break-timer/src/lib/components/MiniTimer.svelte
git commit -m "a11y: fix mini timer paused text contrast for AAA"
```
---
### Task 14: MicrobreakOverlay (`MicrobreakOverlay.svelte`)
**Files:**
- Modify: `break-timer/src/lib/components/MicrobreakOverlay.svelte`
**Step 1: Add alertdialog role, heading, and label**
Change the outer `.microbreak-card` div (line 41):
```svelte
<div class="microbreak-card" role="alertdialog" aria-label="Microbreak" aria-describedby="microbreak-msg">
<h2 class="sr-only">Microbreak</h2>
<div class="flex items-center gap-3 mb-2">
...
<span id="microbreak-msg" class="text-[15px] font-medium text-white">
Look away — 20 feet for {timeRemaining}s
</span>
</div>
```
Replace `text-[#8a8a8a]` with `text-text-sec`.
**Step 2: Verify build**
Run: `cd break-timer && npm run build`
**Step 3: Commit**
```bash
git add break-timer/src/lib/components/MicrobreakOverlay.svelte
git commit -m "a11y: microbreak overlay gets alertdialog role and heading"
```
---
### Task 15: BreakOverlay (`BreakOverlay.svelte`)
**Files:**
- Modify: `break-timer/src/lib/components/BreakOverlay.svelte`
**Step 1: Add alertdialog role, heading, and label**
Change the outer div (line 39):
```svelte
<div
role="alertdialog"
aria-label="Break in progress"
class="fixed inset-0 flex flex-col items-center justify-center"
style="background: rgba(0, 0, 0, {$config.backdrop_opacity});"
>
<h2 class="sr-only">Break in Progress</h2>
<p class="text-[16px] font-medium text-white mb-4">Break in progress</p>
```
**Step 2: Verify build**
Run: `cd break-timer && npm run build`
**Step 3: Commit**
```bash
git add break-timer/src/lib/components/BreakOverlay.svelte
git commit -m "a11y: break overlay gets alertdialog role and heading"
```
---
### Task 16: ColorPicker (`ColorPicker.svelte`)
**Files:**
- Modify: `break-timer/src/lib/components/ColorPicker.svelte`
**Step 1: Enlarge swatches and add aria-pressed**
Change swatch buttons (line 247) from `h-[22px] w-[22px]` to `h-[28px] w-[28px]` with a `min-h-[44px] min-w-[44px]` clickable area (via padding or wrapper).
Add `aria-pressed={value === color}` to each swatch button.
Change the flex container gap from `gap-[6px]` to `gap-[8px]` to accommodate larger swatches.
Replace `text-[#8a8a8a]` with `text-text-sec`.
**Step 2: Verify build**
Run: `cd break-timer && npm run build`
**Step 3: Commit**
```bash
git add break-timer/src/lib/components/ColorPicker.svelte
git commit -m "a11y: enlarge color swatches, add aria-pressed"
```
---
### Task 17: ActivityManager Target Sizes (`ActivityManager.svelte`)
**Files:**
- Modify: `break-timer/src/lib/components/ActivityManager.svelte`
**Step 1: Enlarge star/remove buttons**
Find all favorite (★) and remove (✕) buttons. Change from any `w-8 h-8` / `w-7 h-7` to `w-9 h-9 min-h-[44px] min-w-[44px]`.
Replace `text-[#8a8a8a]` with `text-text-sec`.
**Step 2: Verify build**
Run: `cd break-timer && npm run build`
**Step 3: Commit**
```bash
git add break-timer/src/lib/components/ActivityManager.svelte
git commit -m "a11y: enlarge activity manager action buttons to 44px targets"
```
---
### Task 18: Final Build Verification & Cleanup
**Step 1: Full build**
Run: `cd break-timer && npm run build`
Expected: Build succeeds with zero errors.
**Step 2: Verify all theme token propagation**
Search for any remaining hardcoded `#8a8a8a` in Svelte files — there should be none (all replaced with `text-text-sec` or inline `#a8a8a8`).
Run: `grep -r "#8a8a8a" break-timer/src/` — should return zero results.
**Step 3: Verify no remaining h3 headings in Settings/Stats**
Run: `grep -n "<h3" break-timer/src/lib/components/Settings.svelte break-timer/src/lib/components/StatsView.svelte` — should return zero results.
**Step 4: Final commit if any cleanup needed**
```bash
git add -A break-timer/src/
git commit -m "a11y: final cleanup pass for WCAG 2.2 AAA compliance"
```
---
## Dependency Graph
```
Task 1 (app.css tokens)
├── Task 2 (App.svelte)
├── Task 3 (Titlebar)
├── Task 4 (ToggleSwitch)
├── Task 5 (Stepper)
├── Task 6 (animate.ts)
├── Task 7 (Dashboard) ← depends on Task 6
├── Task 8 (Settings part 1)
├── Task 9 (Settings part 2) ← depends on Task 8
├── Task 10 (StatsView)
├── Task 11 (BreakScreen)
├── Task 12 (Celebration)
├── Task 13 (MiniTimer)
├── Task 14 (MicrobreakOverlay)
├── Task 15 (BreakOverlay)
├── Task 16 (ColorPicker)
└── Task 17 (ActivityManager)
Task 18 (Final verification) ← depends on all above
```
Tasks 217 are mostly independent of each other (except 7 depends on 6, and 9 depends on 8). They all depend on Task 1 being done first.