diff --git a/.specify/templates/constitution-template.md b/.specify/templates/constitution-template.md
index 4e9354e..377e0ce 100644
--- a/.specify/templates/constitution-template.md
+++ b/.specify/templates/constitution-template.md
@@ -70,4 +70,4 @@
**Version**: [CONSTITUTION_VERSION] | **Ratified**: [RATIFICATION_DATE] | **Last Amended**: [LAST_AMENDED_DATE]
-
+
diff --git a/.windsurf/rules/specify-rules.md b/.windsurf/rules/specify-rules.md
index a134e2a..c1cdb6e 100644
--- a/.windsurf/rules/specify-rules.md
+++ b/.windsurf/rules/specify-rules.md
@@ -4,6 +4,9 @@ Auto-generated from all feature plans. Last updated: 2026-02-14
## Active Technologies
+- TypeScript 5 (React 19) + React 19, Tailwind CSS 4 (001-dark-mode)
+- localStorage for theme persistence (001-dark-mode)
+
- TypeScript 5 (React 19) + React 19, Vite 7, Vitest 4, Tailwind CSS 4 (001-multiple-words)
- localStorage for user preferences (001-multiple-words)
@@ -30,11 +33,11 @@ TypeScript 5 (strict) with React 19: Follow standard conventions
## Recent Changes
+- 001-dark-mode: Added TypeScript 5 (React 19) + React 19, Tailwind CSS 4
+
- 001-multiple-words: Added TypeScript 5 (React 19) + React 19, Vite 7, Vitest 4, Tailwind CSS 4
- 001-component-refactor: Added TypeScript 5 (strict mode) with React 19 + React 19, Vite 7, Vitest 4, Tailwind CSS 4
-- 001-speed-reading-app: Added TypeScript 5 (strict) with React 19 + React 19, React DOM 19, Vite 7, Tailwind CSS 4
-
diff --git a/AGENTS.md b/AGENTS.md
index 91465ea..b5c7c1b 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -19,7 +19,7 @@ You're an expert engineer for this React app.
- Vite 7 (build tool)
- Vitest 4 (testing framework)
- Node.js 24
- - Tailwind CSS 4
+ - Tailwind CSS 4 (with dark mode support)
- ESLint 9 with TypeScript support
- Prettier with Tailwind plugin
- React Compiler (babel-plugin-react-compiler)
@@ -108,6 +108,7 @@ import type { User } from './types';
### Testing Standards
+- **TDD** - tests MUST be written first and validated before implementation (red, green, refactor)
- **100% coverage required** - all statements, branches, functions, and lines (except for barrel exports)
- **Do not test barrel exports** - index.ts files are barrel exports and should not have dedicated tests
- **Testing Library** - use @testing-library/react for component testing
diff --git a/index.html b/index.html
index fae2401..5ec0e6a 100644
--- a/index.html
+++ b/index.html
@@ -10,6 +10,30 @@
Speed Reader
+
+
+
+
diff --git a/specs/001-dark-mode/checklists/accessibility.md b/specs/001-dark-mode/checklists/accessibility.md
new file mode 100644
index 0000000..8a898e5
--- /dev/null
+++ b/specs/001-dark-mode/checklists/accessibility.md
@@ -0,0 +1,52 @@
+# Accessibility & Responsive Test Checklist: Dark Mode
+
+**Purpose**: Validate accessibility and responsive design for dark mode feature
+**Created**: 2026-02-15
+**Feature**: Dark Mode
+
+## Keyboard Navigation
+
+- [x] ThemeToggle is focusable via Tab key
+- [x] ThemeToggle activates with Enter key
+- [x] ThemeToggle activates with Space key
+- [x] Focus indicator is visible (2px outline)
+- [x] Focus order is logical and predictable
+
+## Semantics & ARIA
+
+- [x] ThemeToggle has proper ARIA label describing current state
+- [x] ThemeToggle role is "button"
+- [x] Theme changes are announced to screen readers
+- [x] No ARIA violations detected
+
+## Responsive Design
+
+- [x] ThemeToggle is visible and functional on mobile (320px+)
+- [x] ThemeToggle is visible and functional on tablet (768px+)
+- [x] ThemeToggle is visible and functional on desktop (1024px+)
+- [x] Touch target meets minimum 48px × 48px requirement
+- [x] No layout shifts occur during theme transitions
+
+## Color Contrast
+
+- [x] Light mode text meets WCAG AA contrast ratio (4.5:1)
+- [x] Dark mode text meets WCAG AA contrast ratio (4.5:1)
+- [x] Focus indicators meet contrast requirements
+- [x] Interactive elements meet contrast requirements
+
+## High Contrast Mode
+
+- [x] High contrast mode is detected correctly
+- [x] High contrast mode overrides dark mode when active
+- [x] Theme remains functional in high contrast mode
+
+## Reduced Motion
+
+- [x] Theme transitions respect prefers-reduced-motion
+- [x] No animations when reduced motion is preferred
+- [x] Functionality remains intact without animations
+
+## Notes
+
+- All items must be checked before feature is considered complete
+- Use browser DevTools and accessibility testing tools for validation
diff --git a/specs/001-dark-mode/checklists/requirements.md b/specs/001-dark-mode/checklists/requirements.md
new file mode 100644
index 0000000..6f91f98
--- /dev/null
+++ b/specs/001-dark-mode/checklists/requirements.md
@@ -0,0 +1,34 @@
+# Specification Quality Checklist: Dark Mode
+
+**Purpose**: Validate specification completeness and quality before proceeding to planning
+**Created**: 2026-02-15
+**Feature**: [Dark Mode](spec.md)
+
+## Content Quality
+
+- [x] No implementation details (languages, frameworks, APIs)
+- [x] Focused on user value and business needs
+- [x] Written for non-technical stakeholders
+- [x] All mandatory sections completed
+
+## Requirement Completeness
+
+- [x] No [NEEDS CLARIFICATION] markers remain
+- [x] Requirements are testable and unambiguous
+- [x] Success criteria are measurable
+- [x] Success criteria are technology-agnostic (no implementation details)
+- [x] All acceptance scenarios are defined
+- [x] Edge cases are identified
+- [x] Scope is clearly bounded
+- [x] Dependencies and assumptions identified
+
+## Feature Readiness
+
+- [x] All functional requirements have clear acceptance criteria
+- [x] User scenarios cover primary flows
+- [x] Feature meets measurable outcomes defined in Success Criteria
+- [x] No implementation details leak into specification
+
+## Notes
+
+- All checklist items completed - specification is ready for `/speckit.plan`
diff --git a/specs/001-dark-mode/contracts/component-contracts.md b/specs/001-dark-mode/contracts/component-contracts.md
new file mode 100644
index 0000000..2065fe0
--- /dev/null
+++ b/specs/001-dark-mode/contracts/component-contracts.md
@@ -0,0 +1,198 @@
+# Component Contracts: Dark Mode
+
+**Feature**: Dark Mode
+**Date**: 2026-02-15
+**Phase**: 1 - Design
+
+## ThemeToggle Component Contract
+
+### Interface Definition
+
+```typescript
+interface ThemeToggleProps {
+ /** Current theme state */
+ currentTheme: 'light' | 'dark' | 'system';
+ /** Callback when theme is toggled */
+ onThemeToggle: () => void;
+ /** Optional CSS class name */
+ className?: string;
+ /** Whether the toggle should be disabled */
+ disabled?: boolean;
+}
+```
+
+### Behavioral Contract
+
+#### User Interactions
+
+1. **Click Action**
+ - **Trigger**: User clicks the toggle button
+ - **Action**: Calls `onThemeToggle()` callback
+ - **Visual Feedback**: Shows sun/moon icon change
+
+2. **Keyboard Navigation**
+ - **Tab Order**: Toggle must be focusable and included in tab sequence
+ - **Enter/Space**: Activates toggle when focused
+ - **Focus Indicator**: Visible focus state with 2px outline
+
+3. **Screen Reader Support**
+ - **ARIA Label**: "Toggle dark mode, currently {light/dark} mode"
+ - **ARIA Role**: `button`
+ - **State Announcement**: Theme change announced to screen readers
+
+#### Visual Requirements
+
+1. **Position**: Fixed position, bottom-right corner
+2. **Size**: 48px × 48px minimum touch target
+3. **Icons**: Sun icon for light mode, moon icon for dark mode
+4. **Hover State**: Slight scale increase and background color change
+
+### Accessibility Contract
+
+```typescript
+interface AccessibilityRequirements {
+ /** Minimum touch target size in pixels */
+ minTouchTarget: 48;
+ /** Minimum color contrast ratio for normal text */
+ minContrastRatio: 4.5;
+ /** Keyboard support required */
+ keyboardSupport: true;
+ /** Screen reader support required */
+ screenReaderSupport: true;
+}
+```
+
+## useTheme Hook Contract
+
+### Interface Definition
+
+```typescript
+interface UseThemeReturn {
+ /** Currently applied theme */
+ theme: 'light' | 'dark';
+ /** User's preference setting */
+ preference: 'light' | 'dark' | 'system';
+ /** Whether system preference is being followed */
+ followingSystem: boolean;
+ /** Toggle between light and dark themes */
+ toggleTheme: () => void;
+ /** Set specific theme preference */
+ setTheme: (theme: 'light' | 'dark' | 'system') => void;
+ /** Whether high contrast mode is active */
+ highContrastMode: boolean;
+}
+```
+
+### Behavioral Contract
+
+#### Theme Management
+
+1. **Initialization**
+ - Load preference from localStorage if available
+ - Detect system theme as fallback
+ - Apply theme before rendering to prevent flash
+
+2. **Persistence**
+ - Save user preference to localStorage on change
+ - Include timestamp for debugging
+ - Handle storage errors gracefully
+
+3. **System Detection**
+ - Listen for system theme changes
+ - Update theme automatically when following system
+ - Clean up listeners on unmount
+
+#### Error Handling
+
+```typescript
+interface ErrorHandling {
+ /** Fallback behavior when localStorage fails */
+ localStorageFallback: 'system-preference';
+ /** Behavior when string validation fails */
+ parseErrorFallback: 'use-system-preference';
+ /** Behavior when media queries not supported */
+ mediaQueryFallback: 'assume-no-preference';
+}
+```
+
+**Approach**: Simple try-catch blocks following existing `storage.ts` pattern with silent failure and system preference fallback.
+
+## Theme Utility Contract
+
+### Interface Definition
+
+```typescript
+interface ThemeUtils {
+ /** Get system theme preference */
+ getSystemTheme: () => 'light' | 'dark' | 'no-preference';
+ /** Check if high contrast mode is active */
+ getHighContrastMode: () => boolean;
+ /** Save theme preference to localStorage */
+ saveThemePreference: (theme: 'light' | 'dark' | 'system') => boolean;
+ /** Load theme preference from localStorage */
+ loadThemePreference: () => 'light' | 'dark' | 'system' | null;
+ /** Validate theme preference string */
+ validateThemePreference: (
+ data: unknown,
+ ) => data is 'light' | 'dark' | 'system';
+}
+```
+
+### Storage Contract
+
+#### localStorage Schema
+
+```typescript
+interface StorageContract {
+ /** Storage key for theme preference */
+ key: 'speedreader.theme';
+ /** Data format stored */
+ format: 'light' | 'dark' | 'system';
+}
+```
+
+#### Error Handling
+
+Follow existing codebase pattern with simple try-catch blocks:
+
+```typescript
+interface ErrorHandling {
+ /** Fallback behavior when localStorage fails */
+ localStorageFallback: 'system-preference';
+ /** Error handling approach */
+ errorStrategy: 'silent-failure-with-default';
+}
+```
+
+**Storage Operations**:
+
+- All localStorage operations wrapped in try-catch blocks
+- On any error, fall back to system preference
+- No explicit error logging or data corruption detection
+- Follows same pattern as existing `storage.ts` utility
+
+## Integration Contract
+
+### App Component Integration
+
+```typescript
+interface AppThemeIntegration {
+ /** Theme must be applied to entire app */
+ providerLocation: 'root';
+ /** Theme must be applied before first render */
+ initializationTiming: 'before-render';
+ /** Theme changes must not cause layout shifts */
+ layoutStability: 'no-shift';
+}
+```
+
+### CSS Integration
+
+```typescript
+interface CSSIntegration {
+ /** Tailwind class strategy */
+ tailwindStrategy: 'class';
+ /** Custom properties for theme colors */
+ cssVariables: boolean;
+}
+```
diff --git a/specs/001-dark-mode/data-model.md b/specs/001-dark-mode/data-model.md
new file mode 100644
index 0000000..bd4e927
--- /dev/null
+++ b/specs/001-dark-mode/data-model.md
@@ -0,0 +1,138 @@
+# Data Model: Dark Mode
+
+**Feature**: Dark Mode
+**Date**: 2026-02-15
+**Phase**: 1 - Design
+
+## Core Entities
+
+### ThemePreference
+
+Represents the user's theme selection and system behavior.
+
+```typescript
+interface ThemePreference {
+ /** Current theme state */
+ theme: 'light' | 'dark' | 'system';
+ /** Whether theme preference should persist across sessions */
+ persist: boolean;
+ /** Timestamp of last theme change */
+ lastChanged: number;
+}
+```
+
+### SystemTheme
+
+Represents the operating system's current theme preference.
+
+```typescript
+interface SystemTheme {
+ /** System's current color scheme preference */
+ prefersColorScheme: 'light' | 'dark' | 'no-preference';
+ /** Whether system supports color scheme detection */
+ supported: boolean;
+}
+```
+
+### ThemeState
+
+Combined state representing the effective theme applied to the UI.
+
+```typescript
+interface ThemeState {
+ /** The theme currently applied to the UI */
+ effectiveTheme: 'light' | 'dark';
+ /** User's preference setting */
+ userPreference: ThemePreference;
+ /** System's current preference */
+ systemPreference: SystemTheme;
+ /** Whether high contrast mode is active */
+ highContrastMode: boolean;
+}
+```
+
+## State Transitions
+
+### Theme Toggle Flow
+
+```mermaid
+stateDiagram-v2
+ [*] --> Light
+ Light --> Dark: User clicks toggle
+ Dark --> Light: User clicks toggle
+
+ Light --> System: User selects system preference
+ Dark --> System: User selects system preference
+ System --> Light: System changes to light
+ System --> Dark: System changes to dark
+```
+
+### Initialization Flow
+
+```mermaid
+flowchart TD
+ A[App Start] --> B{localStorage available?}
+ B -->|Yes| C[Load stored preference]
+ B -->|No| D[Detect system preference]
+ C --> E{Valid preference?}
+ E -->|Yes| F[Apply stored theme]
+ E -->|No| D
+ D --> G[Apply system theme]
+ F --> H[Render UI]
+ G --> H
+```
+
+## Validation Rules
+
+### Theme Preference Validation
+
+- `theme` must be one of: 'light', 'dark', 'system'
+- `persist` must be boolean
+- `lastChanged` must be valid Unix timestamp
+- Invalid localStorage data should be ignored and fall back to system preference
+
+### System Theme Detection
+
+- `prefersColorScheme` detection uses `window.matchMedia('(prefers-color-scheme: dark)')`
+- `supported` is true if `matchMedia` is available and responds
+- `no-preference` used when system doesn't express a preference
+
+### High Contrast Mode Detection
+
+- Uses `window.matchMedia('(prefers-contrast: high)')`
+- Takes precedence over dark mode when active
+- Falls back gracefully if not supported
+
+## Storage Schema
+
+### localStorage Key
+
+```typescript
+const THEME_STORAGE_KEY = 'speedreader.theme';
+```
+
+### Stored Data Format
+
+```typescript
+// Simple string value
+'light' | 'dark' | 'system';
+```
+
+### Storage Validation
+
+- Validate string against allowed theme values
+- Handle invalid values gracefully by using system preference
+
+## Performance Considerations
+
+- Theme state updates should be batched to prevent re-renders
+- System theme listeners should be properly cleaned up
+- localStorage reads/writes minimized to essential operations
+- Theme transitions use CSS for optimal performance
+
+## Error Handling
+
+- localStorage quota exceeded: fall back to system preference
+- Invalid string values: fall back to system preference
+- Media query not supported: assume 'no-preference'
+- Theme toggle errors: maintain current theme, use simple try-catch
diff --git a/specs/001-dark-mode/plan.md b/specs/001-dark-mode/plan.md
new file mode 100644
index 0000000..4774f5a
--- /dev/null
+++ b/specs/001-dark-mode/plan.md
@@ -0,0 +1,117 @@
+# Implementation Plan: Dark Mode
+
+**Branch**: `001-dark-mode` | **Date**: 2026-02-15 | **Spec**: [spec.md](./spec.md)
+**Input**: Feature specification from `/specs/001-dark-mode/spec.md`
+
+**Note**: This template is filled in by the `/speckit.plan` command. See `.specify/templates/commands/plan.md` for the execution workflow.
+
+## Summary
+
+Add dark mode functionality to the speed reader application with a floating toggle button, theme persistence, and system theme detection. The feature will provide immediate visual feedback with smooth transitions and maintain accessibility standards.
+
+## Technical Context
+
+**Language/Version**: TypeScript 5 (React 19)
+**Primary Dependencies**: React 19, Tailwind CSS 4
+**Storage**: localStorage for theme persistence
+**Testing**: Vitest 4, React Testing Library
+**Target Platform**: Web browser
+**Project Type**: Web application
+**Performance Goals**: Instant theme toggle, no layout shifts
+**Constraints**: Must work with localStorage disabled, respect high contrast mode
+**Scale/Scope**: Single page application with theme-aware components
+
+## Constitution Check
+
+_GATE: Must pass before Phase 0 research. Re-check after Phase 1 design._
+
+### Pre-Design Evaluation ✅
+
+- [x] Reader comprehension impact is defined and measurable for the feature.
+- [x] Deterministic behavior is specified for timing/state transitions and has regression guardrails.
+- [x] Accessibility requirements cover keyboard navigation, semantics, focus, and responsive parity.
+- [x] Test strategy includes regression coverage and required quality gates (`lint`, `lint:tsc`, `test:ci`).
+- [x] Scope is minimal and dependency changes are justified.
+
+### Post-Design Evaluation ✅
+
+- [x] **Reader Comprehension**: Dark mode reduces eye strain in low-light conditions, improving reading comfort and potentially extending reading sessions without fatigue (measurable through user session duration).
+- [x] **Deterministic Behavior**: Theme changes apply instantly, state management through custom useTheme hook ensures predictable behavior, localStorage persistence provides reliable session-to-session consistency.
+- [x] **Accessibility**: ThemeToggle component includes keyboard navigation, ARIA labels, proper focus management, respects high contrast mode and reduced motion preferences.
+- [x] **Test Strategy**: Comprehensive test coverage including unit tests for hooks, component tests for ThemeToggle, integration tests for theme persistence, and required quality gates.
+- [x] **Scope Minimal**: Uses existing React/Tailwind stack, no new dependencies required, leverages browser native APIs (localStorage, matchMedia).
+
+## Project Structure
+
+### Documentation (this feature)
+
+```text
+specs/[###-feature]/
+├── plan.md # This file (/speckit.plan command output)
+├── research.md # Phase 0 output (/speckit.plan command)
+├── data-model.md # Phase 1 output (/speckit.plan command)
+├── quickstart.md # Phase 1 output (/speckit.plan command)
+├── contracts/ # Phase 1 output (/speckit.plan command)
+└── tasks.md # Phase 2 output (/speckit.tasks command - NOT created by /speckit.plan)
+```
+
+### Source Code (repository root)
+
+```text
+src/
+├── components/
+│ ├── App/
+│ │ ├── App.tsx
+│ │ ├── App.types.ts
+│ │ └── App.test.tsx
+│ ├── ThemeToggle/
+│ │ ├── ThemeToggle.tsx
+│ │ ├── ThemeToggle.types.ts
+│ │ └── ThemeToggle.test.tsx
+│ └── ...
+├── hooks/
+│ ├── useTheme.ts
+│ └── useTheme.test.ts
+├── utils/
+│ ├── theme.ts
+│ └── theme.test.ts
+└── types/
+ └── index.ts
+```
+
+**Structure Decision**: Single web application using React 19 with TypeScript. Theme functionality will be encapsulated in custom hooks and utility functions, with a dedicated ThemeToggle component. The existing component structure will be extended rather than restructured.
+
+## Complexity Tracking
+
+> **Fill ONLY if Constitution Check has violations that must be justified**
+
+| Violation | Why Needed | Simpler Alternative Rejected Because |
+| --------- | ---------- | -------------------------------------------- |
+| None | N/A | All requirements met with minimal complexity |
+
+## Phase Completion Status
+
+### Phase 0: Research ✅ COMPLETED
+
+- [x] Technology decisions documented
+- [x] Performance considerations analyzed
+- [x] Accessibility strategy defined
+- [x] Edge cases identified and solutions planned
+
+### Phase 1: Design ✅ COMPLETED
+
+- [x] Data model with entities and state transitions defined
+- [x] Component contracts created with interface definitions
+- [x] Implementation quickstart guide generated
+- [x] Agent context updated with new technology information
+
+### Phase 2: Tasks ⏸️ PENDING
+
+- [ ] Run `/speckit.tasks` to generate actionable implementation tasks
+- [ ] Execute tasks following dependency order
+
+## Ready for Implementation
+
+The dark mode feature is fully planned and ready for implementation. All constitution requirements have been met, technical decisions have been documented, and comprehensive design artifacts have been created.
+
+**Next Step**: Execute `/speckit.tasks` to generate the detailed task breakdown for development.
diff --git a/specs/001-dark-mode/quickstart.md b/specs/001-dark-mode/quickstart.md
new file mode 100644
index 0000000..4efee31
--- /dev/null
+++ b/specs/001-dark-mode/quickstart.md
@@ -0,0 +1,488 @@
+# Quickstart: Dark Mode Implementation
+
+**Feature**: Dark Mode
+**Date**: 2026-02-15
+**Phase**: 1 - Design
+
+## Implementation Overview
+
+This quickstart guide provides the step-by-step approach to implement dark mode functionality in the speed reader application. The implementation follows the React 19 + TypeScript 5 + Tailwind CSS 4 stack.
+
+## Prerequisites
+
+- React 19 with TypeScript 5 strict mode
+- Tailwind CSS 4 configured with `class` dark mode strategy
+- Vitest 4 for testing
+- Existing component structure in `src/components/`
+
+## Step 1: Configure Tailwind CSS Dark Mode
+
+**Tailwind CSS v4** uses CSS-based configuration. Add dark mode variant to `src/index.css`:
+
+```css
+@import 'tailwindcss';
+
+@theme {
+ --color-*: initial;
+ --color-gray-50: #f9fafb;
+ --color-gray-100: #f3f4f6;
+ --color-gray-200: #e5e7eb;
+ --color-gray-300: #d1d5db;
+ --color-gray-400: #9ca3af;
+ --color-gray-500: #6b7280;
+ --color-gray-600: #4b5563;
+ --color-gray-700: #374151;
+ --color-gray-800: #1f2937;
+ --color-gray-900: #111827;
+ --color-slate-100: #f1f5f9;
+ --color-slate-200: #e2e8f0;
+ --color-slate-300: #cbd5e1;
+ --color-slate-700: #334155;
+ --color-slate-900: #0f172a;
+}
+
+@variant dark (&:where(.dark, .dark *));
+```
+
+This enables class-based dark mode where styles are applied when `.dark` class is present on an element or its ancestors.
+
+## Step 2: Create Theme Types
+
+Create `src/types/theme.ts`:
+
+```typescript
+export type Theme = 'light' | 'dark' | 'system';
+
+export interface ThemeState {
+ effectiveTheme: 'light' | 'dark';
+ userPreference: Theme;
+ systemPreference: 'light' | 'dark' | 'no-preference';
+ highContrastMode: boolean;
+}
+```
+
+## Step 3: Implement Theme Utilities
+
+Create `src/utils/theme.ts`:
+
+```typescript
+import type { Theme } from 'src/types/theme';
+
+const THEME_STORAGE_KEY = 'speedreader.theme';
+
+export const getSystemTheme = (): 'light' | 'dark' | 'no-preference' => {
+ if (!window.matchMedia) return 'no-preference';
+
+ return window.matchMedia('(prefers-color-scheme: dark)').matches
+ ? 'dark'
+ : 'light';
+};
+
+export const getHighContrastMode = (): boolean => {
+ if (!window.matchMedia) return false;
+
+ return window.matchMedia('(prefers-contrast: high)').matches;
+};
+
+export const saveThemePreference = (theme: Theme): boolean => {
+ try {
+ localStorage.setItem(THEME_STORAGE_KEY, theme);
+ return true;
+ } catch {
+ return false;
+ }
+};
+
+export const loadThemePreference = (): Theme | null => {
+ try {
+ const stored = localStorage.getItem(THEME_STORAGE_KEY);
+ if (!stored) return null;
+
+ return validateThemePreference(stored) ? stored : null;
+ } catch {
+ return null;
+ }
+};
+
+export const validateThemePreference = (data: unknown): data is Theme => {
+ return typeof data === 'string' && ['light', 'dark', 'system'].includes(data);
+};
+```
+
+## Step 4: Create useTheme Hook
+
+Create `src/hooks/useTheme.ts`:
+
+```typescript
+import { useState, useEffect, useCallback } from 'react';
+import type { Theme, ThemeState } from 'src/types/theme';
+import {
+ getSystemTheme,
+ getHighContrastMode,
+ saveThemePreference,
+ loadThemePreference,
+} from 'src/utils/theme';
+
+const DEFAULT_PREFERENCE: Theme = 'system';
+
+export const useTheme = () => {
+ const [themeState, setThemeState] = useState(() => {
+ const stored = loadThemePreference();
+ const systemTheme = getSystemTheme();
+ const highContrast = getHighContrastMode();
+
+ const preference = stored || DEFAULT_PREFERENCE;
+ const effectiveTheme = highContrast
+ ? 'light'
+ : preference === 'system'
+ ? systemTheme
+ : preference;
+
+ return {
+ effectiveTheme,
+ userPreference: preference,
+ systemPreference: systemTheme,
+ highContrastMode: highContrast,
+ };
+ });
+
+ // Listen for system theme changes
+ useEffect(() => {
+ const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
+
+ const handleChange = () => {
+ setThemeState((prev) => {
+ if (prev.userPreference !== 'system') return prev;
+
+ const newSystemTheme = mediaQuery.matches ? 'dark' : 'light';
+ const effectiveTheme = prev.highContrastMode ? 'light' : newSystemTheme;
+
+ return {
+ ...prev,
+ systemPreference: newSystemTheme,
+ effectiveTheme,
+ };
+ });
+ };
+
+ mediaQuery.addEventListener('change', handleChange);
+ return () => mediaQuery.removeEventListener('change', handleChange);
+ }, []);
+
+ // Listen for high contrast mode changes
+ useEffect(() => {
+ const mediaQuery = window.matchMedia('(prefers-contrast: high)');
+
+ const handleChange = () => {
+ setThemeState((prev) => {
+ const highContrast = mediaQuery.matches;
+ const effectiveTheme = highContrast
+ ? 'light'
+ : prev.userPreference === 'system'
+ ? prev.systemPreference
+ : prev.userPreference;
+
+ return {
+ ...prev,
+ highContrastMode: highContrast,
+ effectiveTheme,
+ };
+ });
+ };
+
+ mediaQuery.addEventListener('change', handleChange);
+ return () => mediaQuery.removeEventListener('change', handleChange);
+ }, []);
+
+ const toggleTheme = useCallback(() => {
+ setThemeState((prev) => {
+ // Cycle through: light → dark → system → light
+ const cycleMap: Record = {
+ light: 'dark',
+ dark: 'system',
+ system: 'light',
+ };
+ const newPreference = cycleMap[prev.userPreference];
+
+ saveThemePreference(newPreference);
+
+ const effectiveTheme =
+ prev.highContrastMode || newPreference === 'system'
+ ? resolveEffectiveTheme(prev.systemPreference)
+ : newPreference;
+
+ return {
+ ...prev,
+ effectiveTheme,
+ userPreference: newPreference,
+ };
+ });
+ }, []);
+
+ const setTheme = useCallback((theme: Theme) => {
+ setThemeState((prev) => {
+ saveThemePreference(theme);
+
+ const effectiveTheme = prev.highContrastMode
+ ? 'light'
+ : theme === 'system'
+ ? prev.systemPreference
+ : theme;
+
+ return {
+ ...prev,
+ effectiveTheme,
+ userPreference: theme,
+ };
+ });
+ }, []);
+
+ return {
+ theme: themeState.effectiveTheme,
+ preference: themeState.userPreference,
+ followingSystem: themeState.userPreference === 'system',
+ toggleTheme,
+ setTheme,
+ highContrastMode: themeState.highContrastMode,
+ };
+};
+```
+
+## Step 5: Create ThemeToggle Component
+
+Create `src/components/ThemeToggle/ThemeToggle.tsx`:
+
+```typescript
+import type { ThemeToggleProps } from './ThemeToggle.types';
+
+export const ThemeToggle = ({
+ currentTheme,
+ onThemeToggle,
+ disabled = false,
+}: ThemeToggleProps) => {
+ const ariaLabel = `Toggle theme, currently ${currentTheme} mode. Click to cycle to ${
+ currentTheme === 'light' ? 'dark' : currentTheme === 'dark' ? 'system' : 'light'
+ } mode`;
+
+ const renderIcon = () => {
+ switch (currentTheme) {
+ case 'dark':
+ return (
+
+ );
+ case 'system':
+ return (
+
+ );
+ case 'light':
+ default:
+ return (
+
+ );
+ }
+ };
+
+ return (
+
+ );
+};
+```
+
+Create `src/components/ThemeToggle/ThemeToggle.types.ts`:
+
+```typescript
+export interface ThemeToggleProps {
+ currentTheme: 'light' | 'dark' | 'system';
+ onThemeToggle: () => void;
+ className?: string;
+ disabled?: boolean;
+}
+```
+
+## Step 6: Integrate with App Component
+
+Update `src/components/App/App.tsx`:
+
+```typescript
+import { useTheme } from 'src/hooks/useTheme';
+import { ThemeToggle } from 'src/components/ThemeToggle';
+import './App.css';
+
+export const App = () => {
+ const { preference, toggleTheme } = useTheme();
+
+ // Theme is automatically applied to document root by useTheme hook
+
+ return (
+
+ {/* Your existing app content */}
+
+
+
+ );
+};
+```
+
+**Note**: The `useTheme` hook now handles applying the theme class to `document.documentElement` internally, so you don't need a separate `useEffect`. Pass `preference` (not `theme`) to `ThemeToggle` to show the user's preference (light/dark/system) rather than the effective theme.
+
+## Step 7: Add Tests
+
+Create `src/hooks/useTheme.test.ts`:
+
+```typescript
+import { renderHook, act } from '@testing-library/react';
+import { useTheme } from './useTheme';
+
+// Mock localStorage
+const localStorageMock = {
+ getItem: vi.fn(),
+ setItem: vi.fn(),
+ removeItem: vi.fn(),
+ clear: vi.fn(),
+};
+Object.defineProperty(window, 'localStorage', { value: localStorageMock });
+
+// Mock matchMedia
+Object.defineProperty(window, 'matchMedia', {
+ writable: true,
+ value: vi.fn().mockImplementation((query) => ({
+ matches: false,
+ media: query,
+ onchange: null,
+ addListener: vi.fn(),
+ removeListener: vi.fn(),
+ addEventListener: vi.fn(),
+ removeEventListener: vi.fn(),
+ dispatchEvent: vi.fn(),
+ })),
+});
+
+describe('useTheme', () => {
+ beforeEach(() => {
+ localStorageMock.getItem.mockReturnValue(null);
+ });
+
+ it('should initialize with system preference', () => {
+ const { result } = renderHook(() => useTheme());
+
+ expect(result.current.theme).toBe('light');
+ expect(result.current.preference).toBe('system');
+ });
+
+ it('should toggle theme', () => {
+ const { result } = renderHook(() => useTheme());
+
+ act(() => {
+ result.current.toggleTheme();
+ });
+
+ expect(result.current.theme).toBe('dark');
+ expect(result.current.preference).toBe('dark');
+ });
+
+ it('should load saved preference from localStorage', () => {
+ const savedPreference = 'dark';
+
+ localStorageMock.getItem.mockReturnValue(savedPreference);
+
+ const { result } = renderHook(() => useTheme());
+
+ expect(result.current.theme).toBe('dark');
+ expect(result.current.preference).toBe('dark');
+ });
+});
+```
+
+## Step 8: Apply Theme Classes to Components
+
+**Apply theme classes to components**:
+
+```tsx
+// Main App container
+
+ {/* Header text */}
+
+
+ Speed Reader
+
+
+ Paste text, choose your pace, and read one word at a time.
+
+
+
+ {/* Card section */}
+
+ {/* Content */}
+
+
+ {/* ThemeToggle */}
+
+
+```
+
+## Verification Steps
+
+1. **Build and Test**: Run `npm run build` and `npm run test:ci`
+2. **Manual Testing**:
+ - Toggle between themes
+ - Refresh browser to verify persistence
+ - Change system theme to verify automatic detection
+3. **Accessibility Testing**:
+ - Test keyboard navigation
+ - Verify screen reader announcements
+ - Check color contrast ratios
+
+## Next Steps
+
+After implementation, run `/speckit.tasks` to generate the detailed task breakdown for development.
diff --git a/specs/001-dark-mode/research.md b/specs/001-dark-mode/research.md
new file mode 100644
index 0000000..3ceddbb
--- /dev/null
+++ b/specs/001-dark-mode/research.md
@@ -0,0 +1,92 @@
+# Research: Dark Mode Implementation
+
+**Feature**: Dark Mode
+**Date**: 2026-02-15
+**Phase**: 0 - Research Complete
+
+## Technology Decisions
+
+### Theme Management Approach
+
+**Decision**: Use custom hook + localStorage + system preference detection
+**Rationale**:
+
+- Custom `useTheme` hook follows existing codebase pattern (like `useReadingSession`)
+- localStorage ensures persistence across sessions
+- `prefers-color-scheme` media query enables system theme detection
+- Simple prop passing to ThemeToggle component (no deep component tree)
+- No additional dependencies required beyond existing React/Tailwind
+
+**Alternatives considered**:
+
+- React Context (rejected: over-engineering for simple use case with shallow component tree)
+- CSS-only solution with `prefers-color-scheme` (rejected: lacks user preference persistence)
+- Third-party theme libraries (rejected: adds unnecessary dependencies for simple use case)
+
+### Theme Toggle Implementation
+
+**Decision**: Custom SVG toggle button with sun/moon icons
+**Rationale**:
+
+- Complete control over styling and animations
+- Lightweight - no external icon dependencies
+- Can be positioned as floating button per requirements
+- Better accessibility control with custom ARIA labels
+
+**Alternatives considered**:
+
+- Icon library (react-icons) (rejected: adds dependency for just 2 icons)
+- Emoji toggle (rejected: inconsistent rendering across platforms)
+
+### Tailwind CSS Dark Mode Strategy
+
+**Decision**: Use Tailwind's `dark:` variant prefix with `class` strategy
+**Rationale**:
+
+- Leverages existing Tailwind setup
+- Provides explicit control over theme application
+- Works well with custom hook state management
+- Maintains design system consistency
+
+**Alternatives considered**:
+
+- Tailwind `media` strategy (rejected: doesn't allow user preference override)
+- Custom CSS variables (rejected: more complex, loses Tailwind utility benefits)
+
+### Storage Strategy
+
+**Decision**: localStorage with fallback to system preference
+**Rationale**:
+
+- Native browser API, no dependencies
+- Sufficient for simple theme preference storage
+- Graceful degradation when localStorage unavailable
+- Meets persistence requirements
+
+**Alternatives considered**:
+
+- IndexedDB (rejected: overkill for simple boolean preference)
+- Cookies (rejected: sent with every request, unnecessary overhead)
+
+## Performance Considerations
+
+- Theme changes apply instantly, no layout shifts expected - theme changes only affect colors, not layout
+- Theme detection happens once on app initialization
+- localStorage access is synchronous and fast
+
+## Accessibility Strategy
+
+- Toggle button will have proper ARIA labels and keyboard support
+- Color contrast ratios will be validated for both light and dark themes
+- High contrast mode detection will override dark mode when detected
+
+## Edge Cases Handled
+
+- localStorage unavailable: fallback to system preference
+- System theme changes during session: automatic detection and update
+- High contrast mode: takes precedence over dark mode
+- Page load timing: wait for stored theme before rendering to prevent flash
+
+## Research Complete
+
+All technical decisions have been documented. No further clarification needed for Phase 1 design.
diff --git a/specs/001-dark-mode/spec.md b/specs/001-dark-mode/spec.md
new file mode 100644
index 0000000..86cae0e
--- /dev/null
+++ b/specs/001-dark-mode/spec.md
@@ -0,0 +1,104 @@
+# Feature Specification: Dark Mode
+
+**Feature Branch**: `001-dark-mode`
+**Created**: 2026-02-15
+**Status**: Draft
+**Input**: User description: "dark mode"
+
+## Clarifications
+
+### Session 2026-02-15
+
+- Q: Toggle Control Location and Type → A: Bottom right floating SVG toggle with sun/moon/monitor icons cycling through light/dark/system
+- Q: System Theme Change Behavior → A: Automatically follow system changes
+- Q: High Contrast Mode Interaction → A: Respect high contrast over dark mode
+- Q: Theme Loading State Behavior → A: Wait for stored theme before showing content
+
+## User Scenarios & Testing _(mandatory)_
+
+### User Story 1 - Toggle Dark Mode (Priority: P1)
+
+User wants to cycle between light, dark, and system themes to reduce eye strain during reading in low-light conditions.
+
+**Why this priority**: Core functionality that provides immediate user value and accessibility benefits.
+
+**Independent Test**: Can be fully tested by cycling through the theme states and verifying the UI changes between light, dark, and system modes.
+
+**Acceptance Scenarios**:
+
+1. **Given** the application is in light mode, **When** user clicks the theme toggle, **Then** the interface switches to dark theme with appropriate colors
+2. **Given** the application is in dark mode, **When** user clicks the theme toggle, **Then** the interface switches to system theme (following OS preference)
+3. **Given** the application is in system mode, **When** user clicks the theme toggle, **Then** the interface switches to light theme
+4. **Given** the application is in system mode following dark OS preference, **When** OS theme changes to light, **Then** the interface automatically updates to light theme
+
+---
+
+### User Story 2 - Persistent Theme Preference (Priority: P2)
+
+User wants their theme preference to be remembered across sessions so they don't have to manually switch each time.
+
+**Why this priority**: Improves user experience by maintaining consistency and reducing friction.
+
+**Independent Test**: Can be tested by setting a theme, closing/reopening the application, and verifying the theme persists.
+
+**Acceptance Scenarios**:
+
+1. **Given** user has selected dark mode, **When** they close and reopen the application, **Then** dark mode is automatically applied
+2. **Given** user has selected light mode, **When** they close and reopen the application, **Then** light mode is automatically applied
+
+---
+
+### User Story 3 - System Theme Detection (Priority: P3)
+
+User wants the application to automatically match their operating system's theme preference.
+
+**Why this priority**: Provides seamless integration with user's system preferences for better UX.
+
+**Independent Test**: Can be tested by changing OS theme settings and verifying the application responds accordingly.
+
+**Acceptance Scenarios**:
+
+1. **Given** user's OS is set to dark mode, **When** they first visit the application, **Then** dark mode is automatically selected
+2. **Given** user's OS is set to light mode, **When** they first visit the application, **Then** light mode is automatically selected
+
+### Edge Cases
+
+- **localStorage disabled**: System MUST default to system theme preference and continue functioning
+- **localStorage quota exceeded**: System MUST gracefully fall back to system theme preference
+- **Theme switching during page load**: System MUST wait for stored theme before showing content (FR-008)
+- **System theme changes while app open**: System MUST automatically update theme when following system preference
+- **High contrast mode activation**: System MUST respect high contrast over dark mode when detected
+
+## Requirements _(mandatory)_
+
+### Constitution Alignment _(mandatory)_
+
+- **Comprehension Outcome**: Dark mode reduces eye strain in low-light conditions, improving reading comfort and potentially extending reading sessions without fatigue.
+- **Deterministic Behavior**: Theme changes must apply within 100ms consistently across all UI elements, with no flickering or partial updates.
+- **Accessibility Coverage**: Theme toggle must be keyboard accessible, properly labeled for screen readers, and maintain sufficient color contrast ratios in both modes.
+
+### Functional Requirements
+
+- **FR-001**: System MUST provide a bottom right floating SVG toggle with sun/moon/monitor icons to cycle between light, dark, and system themes
+- **FR-002**: System MUST apply theme changes within 100ms to all UI elements with no flickering or partial updates
+- **FR-003**: System MUST persist user's theme preference across sessions
+- **FR-004**: System MUST detect and respect user's operating system theme preference on first visit and automatically follow system changes
+- **FR-005**: System MUST maintain proper color contrast ratios for accessibility in both themes
+- **FR-006**: System MUST handle localStorage unavailability gracefully by defaulting to system theme preference
+- **FR-007**: System MUST respect high contrast mode over dark mode when detected
+- **FR-008**: System MUST wait for stored theme before showing content during page load
+
+### Key Entities _(include if feature involves data)_
+
+- **Theme Preference**: User's selected theme (light/dark/system) with persistence across sessions
+- **System Theme**: Operating system's current theme preference for automatic detection
+
+## Success Criteria _(mandatory)_
+
+### Measurable Outcomes
+
+- **SC-001**: Users can toggle between themes within 100ms with immediate visual feedback and no layout shifts
+- **SC-002**: Theme preference persists across 100% of browser sessions when localStorage is available
+- **SC-003**: Both light and dark themes maintain WCAG AA contrast ratios (4.5:1 for normal text)
+- **SC-004**: 95% of users successfully find and use the theme toggle without assistance
+- **SC-005**: System theme detection works correctly on 90% of supported operating systems and browsers
diff --git a/specs/001-dark-mode/tasks.md b/specs/001-dark-mode/tasks.md
new file mode 100644
index 0000000..1fd2f94
--- /dev/null
+++ b/specs/001-dark-mode/tasks.md
@@ -0,0 +1,231 @@
+---
+description: 'Task list template for feature implementation'
+---
+
+# Tasks: Dark Mode
+
+**Input**: Design documents from `/specs/001-dark-mode/`
+**Prerequisites**: plan.md (required), spec.md (required for user stories), data-model.md, contracts/
+
+**Tests**: Test-First Quality Gates enforced - tests MUST be written and validated before implementation for behavior changes, following constitution principle IV.
+
+**Organization**: Tasks are grouped by user story to enable independent implementation and testing of each story.
+
+## Format: `[ID] [P?] [Story] Description`
+
+- **[P]**: Can run in parallel (different files, no dependencies)
+- **[Story]**: Which user story this task belongs to (e.g., US1, US2, US3)
+- Include exact file paths in descriptions
+
+## Path Conventions
+
+- **Single project**: `src/`, `tests/` at repository root
+- **Web app**: `backend/src/`, `frontend/src/`
+- **Mobile**: `api/src/`, `ios/src/` or `android/src/`
+- Paths shown below assume single project - adjust based on plan.md structure
+
+## Phase 1: Setup (Shared Infrastructure)
+
+**Purpose**: Project initialization and basic structure
+
+- [x] T001 Configure Tailwind CSS v4 dark mode with @variant directive in src/index.css
+- [x] T002 [P] Create theme types in src/types/theme.ts
+- [x] T003 [P] Establish accessibility and responsive test checklist for dark mode feature
+
+---
+
+## Phase 2: Foundational (Blocking Prerequisites)
+
+**Purpose**: Core infrastructure that MUST be complete before ANY user story can be implemented
+
+**⚠️ CRITICAL**: No user story work can begin until this phase is complete
+
+- [x] T004 [P] Implement theme utility functions in src/utils/theme.ts
+- [x] T005 [P] Create useTheme hook in src/hooks/useTheme.ts
+- [x] T006 Create ThemeToggle component structure in src/components/ThemeToggle/
+- [x] T007 [P] Create ThemeToggle types in src/components/ThemeToggle/ThemeToggle.types.ts
+
+**Checkpoint**: Foundation ready - user story implementation can now begin in parallel
+
+---
+
+## Phase 3: User Story 1 - Toggle Dark Mode (Priority: P1) 🎯 MVP
+
+**Goal**: User wants to switch between light and dark themes to reduce eye strain during reading in low-light conditions.
+
+**Independent Test**: Can be fully tested by toggling the theme switch and verifying the UI changes between light and dark modes.
+
+### Tests for User Story 1 (TDD REQUIRED)
+
+> **⚠️ TEST-FIRST ENFORCED**: Write tests FIRST, ensure they PASS validation before implementation
+
+- [x] T008 [P] [US1] Write FAILING useTheme hook tests in src/hooks/useTheme.test.ts
+- [x] T009 [P] [US1] Write FAILING theme utility tests in src/utils/theme.test.ts
+- [x] T010 [P] [US1] Write FAILING ThemeToggle component tests in src/components/ThemeToggle/ThemeToggle.test.tsx
+
+### Implementation for User Story 1
+
+- [x] T011 [US1] Implement ThemeToggle component in src/components/ThemeToggle/ThemeToggle.tsx
+- [x] T012 [US1] Create ThemeToggle barrel export in src/components/ThemeToggle/index.ts
+- [x] T013 [US1] Integrate useTheme hook in src/components/App/App.tsx
+- [x] T014 [US1] Apply theme classes to existing components in src/components/App/App.tsx
+- [x] T015 [US1] Add theme transition styles to src/index.css
+- [x] T016 [US1] Implement theme loading state management in src/components/App/App.tsx to wait for stored theme before showing content
+
+**Checkpoint**: At this point, User Story 1 should be fully functional and testable independently
+
+---
+
+## Phase 4: User Story 2 - Persistent Theme Preference (Priority: P2)
+
+**Goal**: User wants their theme preference to be remembered across sessions so they don't have to manually switch each time.
+
+**Independent Test**: Can be tested by setting a theme, closing/reopening the application, and verifying the theme persists.
+
+### Tests for User Story 2 (TDD REQUIRED)
+
+> **⚠️ TEST-FIRST ENFORCED**: Write tests FIRST, ensure they PASS validation before implementation
+
+- [x] T017 [P] [US2] Write FAILING localStorage persistence tests in src/hooks/useTheme.test.ts
+- [x] T018 [P] [US2] Write FAILING localStorage error handling tests in src/utils/theme.test.ts
+
+### Implementation for User Story 2
+
+- [x] T019 [US2] Implement localStorage save functionality in src/utils/theme.ts
+- [x] T020 [US2] Implement localStorage load functionality in src/utils/theme.ts
+- [x] T021 [US2] Add localStorage error handling in src/hooks/useTheme.ts
+- [x] T022 [US2] Test theme persistence across browser sessions
+
+**Checkpoint**: At this point, User Stories 1 AND 2 should both work independently
+
+---
+
+## Phase 5: User Story 3 - System Theme Detection (Priority: P3)
+
+**Goal**: User wants the application to automatically match their operating system's theme preference.
+
+**Independent Test**: Can be tested by changing OS theme settings and verifying the application responds accordingly.
+
+### Tests for User Story 3 (TDD REQUIRED)
+
+> **⚠️ TEST-FIRST ENFORCED**: Write tests FIRST, ensure they PASS validation before implementation
+
+- [x] T023 [P] [US3] Write FAILING system theme detection tests in src/utils/theme.test.ts
+- [x] T024 [P] [US3] Write FAILING system theme change listener tests in src/hooks/useTheme.test.ts
+
+### Implementation for User Story 3
+
+- [x] T025 [US3] Implement system theme detection in src/utils/theme.ts
+- [x] T026 [US3] Add system theme change listeners in src/hooks/useTheme.ts
+- [x] T027 [US3] Implement high contrast mode detection in src/utils/theme.ts
+- [x] T028 [US3] Add high contrast mode handling in src/hooks/useTheme.ts
+- [x] T029 [US3] Update ThemeToggle to show system preference state
+
+**Checkpoint**: All user stories should now be independently functional
+
+---
+
+## Phase 6: Polish & Cross-Cutting Concerns
+
+**Purpose**: Improvements that affect multiple user stories
+
+- [x] T030 Code cleanup and refactoring for theme implementation
+- [x] T031 Performance optimization for theme transitions
+- [x] T032 [P] Accessibility verification across keyboard, semantics, and responsive breakpoints
+- [x] T033 [P] Additional regression tests for theme functionality
+- [x] T034 Security hardening for localStorage usage
+- [x] T035 Execute quality gates: `npm run lint`, `npm run lint:tsc`, `npm run test:ci`
+
+---
+
+## Dependencies & Execution Order
+
+### Phase Dependencies
+
+- **Setup (Phase 1)**: No dependencies - can start immediately
+- **Foundational (Phase 2)**: Depends on Setup completion - BLOCKS all user stories
+- **User Stories (Phase 3+)**: All depend on Foundational phase completion
+ - User stories can then proceed in parallel (if staffed)
+ - Or sequentially in priority order (P1 → P2 → P3)
+- **Polish (Final Phase)**: Depends on all desired user stories being complete
+
+### User Story Dependencies
+
+- **User Story 1 (P1)**: Can start after Foundational (Phase 2) - No dependencies on other stories
+- **User Story 2 (P2)**: Can start after Foundational (Phase 2) - Extends US1 with persistence
+- **User Story 3 (P3)**: Can start after Foundational (Phase 2) - Extends US1/US2 with system detection
+
+### Within Each User Story
+
+- **TEST-FIRST ENFORCED**: Tests MUST be written and validated before implementation (no exceptions)
+- Utilities before hooks
+- Hooks before components
+- Components before integration
+- Story complete before moving to next priority
+
+### Parallel Opportunities
+
+- All Setup tasks marked [P] can run in parallel
+- All Foundational tasks marked [P] can run in parallel (within Phase 2)
+- Once Foundational phase completes, all user stories can start in parallel (if team capacity allows)
+- All tests for a user story marked [P] can run in parallel
+- Different user stories can be worked on in parallel by different team members
+
+---
+
+## Parallel Example: User Story 1 (TDD APPROACH)
+
+```bash
+# Step 1: Write all FAILING tests for User Story 1:
+Task: "Write FAILING useTheme hook tests in src/hooks/useTheme.test.ts"
+Task: "Write FAILING theme utility tests in src/utils/theme.test.ts"
+Task: "Write FAILING ThemeToggle component tests in src/components/ThemeToggle/ThemeToggle.test.tsx"
+
+# Step 2: Verify all tests FAIL, then implement to make them pass
+Task: "Implement ThemeToggle component in src/components/ThemeToggle/ThemeToggle.tsx"
+Task: "Create ThemeToggle barrel export in src/components/ThemeToggle/index.ts"
+```
+
+---
+
+## Implementation Strategy
+
+### MVP First (User Story 1 Only)
+
+1. Complete Phase 1: Setup
+2. Complete Phase 2: Foundational (CRITICAL - blocks all stories)
+3. Complete Phase 3: User Story 1
+4. **STOP and VALIDATE**: Test User Story 1 independently
+5. Deploy/demo if ready
+
+### Incremental Delivery
+
+1. Complete Setup + Foundational → Foundation ready
+2. Add User Story 1 → Test independently → Deploy/Demo (MVP!)
+3. Add User Story 2 → Test independently → Deploy/Demo
+4. Add User Story 3 → Test independently → Deploy/Demo
+5. Each story adds value without breaking previous stories
+
+### Parallel Team Strategy
+
+With multiple developers:
+
+1. Team completes Setup + Foundational together
+2. Once Foundational is done:
+ - Developer A: User Story 1
+ - Developer B: User Story 2
+ - Developer C: User Story 3
+3. Stories complete and integrate independently
+
+---
+
+## Notes
+
+- [P] tasks = different files, no dependencies
+- [Story] label maps task to specific user story for traceability
+- **Test-First ENFORCED**: Tests MUST be written first and validated before implementation
+- Each user story should be independently completable and testable
+- No exceptions to TDD rule for behavior changes
+- Commit after each task or logical group
+- Stop at any checkpoint to validate story independently
+- Avoid: vague tasks, same file conflicts, cross-story dependencies that break independence
diff --git a/specs/001-multiple-words/checklists/requirements.md b/specs/001-multiple-words/checklists/requirements.md
index 9ec227d..3a48d25 100644
--- a/specs/001-multiple-words/checklists/requirements.md
+++ b/specs/001-multiple-words/checklists/requirements.md
@@ -1,7 +1,7 @@
# Specification Quality Checklist: Multiple Words Display
**Purpose**: Validate specification completeness and quality before proceeding to planning
-**Created**: 2025-02-15
+**Created**: 2026-02-15
**Feature**: [Multiple Words Display](../spec.md)
## Content Quality
diff --git a/specs/001-multiple-words/contracts/component-apis.md b/specs/001-multiple-words/contracts/component-apis.md
index dae7ea2..b47bc08 100644
--- a/specs/001-multiple-words/contracts/component-apis.md
+++ b/specs/001-multiple-words/contracts/component-apis.md
@@ -1,6 +1,6 @@
# Component API Contracts: Multiple Words Display
-**Date**: 2025-02-15
+**Date**: 2026-02-15
**Feature**: Multiple Words Display
## ControlPanel Component API
diff --git a/specs/001-multiple-words/data-model.md b/specs/001-multiple-words/data-model.md
index 2a48d46..a4c7899 100644
--- a/specs/001-multiple-words/data-model.md
+++ b/specs/001-multiple-words/data-model.md
@@ -1,6 +1,6 @@
# Data Model: Multiple Words Display
-**Date**: 2025-02-15
+**Date**: 2026-02-15
**Feature**: Multiple Words Display
**Status**: Complete
diff --git a/specs/001-multiple-words/plan.md b/specs/001-multiple-words/plan.md
index 884f033..5484a35 100644
--- a/specs/001-multiple-words/plan.md
+++ b/specs/001-multiple-words/plan.md
@@ -1,6 +1,6 @@
# Implementation Plan: Multiple Words Display
-**Branch**: `001-multiple-words` | **Date**: 2025-02-15 | **Spec**: [spec.md](spec.md)
+**Branch**: `001-multiple-words` | **Date**: 2026-02-15 | **Spec**: [spec.md](spec.md)
**Input**: Feature specification from `/specs/001-multiple-words/spec.md`
**Note**: This template is filled in by the `/speckit.plan` command. See `.specify/templates/commands/plan.md` for the execution workflow.
diff --git a/specs/001-multiple-words/quickstart.md b/specs/001-multiple-words/quickstart.md
index 7985dc0..8bfa994 100644
--- a/specs/001-multiple-words/quickstart.md
+++ b/specs/001-multiple-words/quickstart.md
@@ -1,6 +1,6 @@
# Quickstart Guide: Multiple Words Display
-**Date**: 2025-02-15
+**Date**: 2026-02-15
**Feature**: Multiple Words Display
## Overview
@@ -52,7 +52,7 @@ export function ControlPanel({
id="word-count"
value={wordCount}
onChange={(e) => handleWordCountChange(parseInt(e.target.value, 10))}
- className="w-full rounded-md border border-slate-300 bg-white p-2"
+ className="w-full rounded-md border border-slate-300 p-2"
>
diff --git a/specs/001-multiple-words/research.md b/specs/001-multiple-words/research.md
index 26be080..08756bb 100644
--- a/specs/001-multiple-words/research.md
+++ b/specs/001-multiple-words/research.md
@@ -1,6 +1,6 @@
# Research: Multiple Words Display
-**Date**: 2025-02-15
+**Date**: 2026-02-15
**Feature**: Multiple Words Display
**Status**: Complete
diff --git a/specs/001-multiple-words/spec.md b/specs/001-multiple-words/spec.md
index 7cbf310..dff8a68 100644
--- a/specs/001-multiple-words/spec.md
+++ b/specs/001-multiple-words/spec.md
@@ -1,7 +1,7 @@
# Feature Specification: Multiple Words Display
**Feature Branch**: `001-multiple-words`
-**Created**: 2025-02-15
+**Created**: 2026-02-15
**Status**: **Fully Implemented - All Phases Complete**
**Input**: User description: "multiple words"
@@ -34,7 +34,7 @@
## Clarifications
-### Session 2025-02-15
+### Session 2026-02-15
- Q: What type of UI control should be used for selecting the word count per chunk? → A: Dropdown/select menu with numbered options, min 1 and max 5 words
- Q: What should be the default word count when users first enable multiple words display? → A: 1 word (same as current single-word mode)
diff --git a/specs/001-multiple-words/tasks.md b/specs/001-multiple-words/tasks.md
index 45fc868..59a052b 100644
--- a/specs/001-multiple-words/tasks.md
+++ b/specs/001-multiple-words/tasks.md
@@ -1,6 +1,6 @@
# Implementation Tasks: Multiple Words Display
-**Branch**: `001-multiple-words` | **Date**: 2025-02-15
+**Branch**: `001-multiple-words` | **Date**: 2026-02-15
**Spec**: [spec.md](spec.md) | **Plan**: [plan.md](plan.md)
## Summary
diff --git a/src/components/App/App.tsx b/src/components/App/App.tsx
index 139563e..3d96cdc 100644
--- a/src/components/App/App.tsx
+++ b/src/components/App/App.tsx
@@ -7,12 +7,15 @@ import {
TextInput,
tokenizeContent,
} from 'src/components/TextInput';
+import { ThemeToggle } from 'src/components/ThemeToggle';
+import { useTheme } from 'src/hooks/useTheme';
import { SessionCompletion } from '../SessionCompletion';
import { useReadingSession } from './useReadingSession';
export default function App() {
const [rawText, setRawText] = useState('');
+ const { preference, toggleTheme } = useTheme();
const {
currentWordIndex,
@@ -53,17 +56,17 @@ export default function App() {
};
return (
-
+
-
+
Speed Reader
-
+
Paste text, choose your pace, and read one word at a time.