Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
b5ea823
docs(specs): create dark mode spec
remarkablemark Feb 16, 2026
4538d7c
docs(specs): clarify localStorage fallback
remarkablemark Feb 16, 2026
61639d5
docs(specs): fix date
remarkablemark Feb 16, 2026
65b4074
docs(specs): fix more dates
remarkablemark Feb 16, 2026
00f3abf
docs(specs): clarify toggle icons
remarkablemark Feb 16, 2026
6a427e8
docs(specs): clarify theme transition
remarkablemark Feb 16, 2026
12596b2
docs(specs): clarify system theme change
remarkablemark Feb 16, 2026
3d98478
docs(specs): clarify high contrast mode
remarkablemark Feb 16, 2026
b53632c
docs(specs): clarify loading behavior
remarkablemark Feb 16, 2026
1519a9f
docs(specs): clarify icon and position
remarkablemark Feb 16, 2026
48e9b0c
test(vite): exclude barrel exports and types from test coverage
remarkablemark Feb 16, 2026
77e3d3c
docs(specs): remove comments
remarkablemark Feb 16, 2026
3ef7c06
docs(specs): create plan
remarkablemark Feb 16, 2026
1f60e04
docs(specs): update contracts
remarkablemark Feb 16, 2026
ffb770e
docs(windsurf): update agent context
remarkablemark Feb 16, 2026
3acc874
docs(specs): fix incorrect project structure in plan
remarkablemark Feb 16, 2026
5d593a2
docs(specs): replace React Context with hook
remarkablemark Feb 16, 2026
f16cfa3
docs(specs): update error handling
remarkablemark Feb 16, 2026
55bb7c4
docs(specs): update localStorage key and value
remarkablemark Feb 16, 2026
4a08666
docs(specs): add Tailwind alternative
remarkablemark Feb 16, 2026
d83db6f
docs(specs): remove theme transition
remarkablemark Feb 16, 2026
4ddd8a2
docs(specs): remove unnecessary maxSize
remarkablemark Feb 16, 2026
aa80de1
docs(specs): tidy quickstart
remarkablemark Feb 16, 2026
c6730f9
docs(specs): generate tasks
remarkablemark Feb 16, 2026
44bb859
docs(specs): enforce TDD
remarkablemark Feb 16, 2026
c814c8c
docs(specs): remediate issues about test-first
remarkablemark Feb 16, 2026
c6a525a
docs(specs): remove README task
remarkablemark Feb 16, 2026
419380a
feat: add dark mode
remarkablemark Feb 16, 2026
dae418b
fix: remove bg-white class and get test coverage to 100%
remarkablemark Feb 16, 2026
65c260b
refactor(types): consolidate readerTypes
remarkablemark Feb 16, 2026
5f08160
refactor(components): remove redundant tokenizeContent
remarkablemark Feb 16, 2026
bb3d1d9
fix(ControlPanel): make labels readable in dark mode
remarkablemark Feb 16, 2026
7526448
fix(TextInput): make it readable in dark mode
remarkablemark Feb 16, 2026
aa7e536
fix(Button): make it readable in dark mode
remarkablemark Feb 16, 2026
f2817f2
fix(SessionDetails): make text readable in dark mode
remarkablemark Feb 16, 2026
82efd68
fix(ReadingDisplay): adjust styles for dark mode
remarkablemark Feb 16, 2026
491c16c
fix(ThemeToggle): adjust colors in dark/light mode
remarkablemark Feb 16, 2026
3782d8f
feat(ThemeToggle): add three-state theme toggle (system, light, dark)
remarkablemark Feb 16, 2026
a1de7f9
fix: fix flash of white in dark mode
remarkablemark Feb 16, 2026
6c5a70c
refactor(hook): tidy constant in useTheme
remarkablemark Feb 16, 2026
8b7424a
docs(specs): mark accessibility checklist as complete
remarkablemark Feb 16, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .specify/templates/constitution-template.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,4 +70,4 @@

**Version**: [CONSTITUTION_VERSION] | **Ratified**: [RATIFICATION_DATE] | **Last Amended**: [LAST_AMENDED_DATE]

<!-- Example: Version: 2.1.1 | Ratified: 2025-06-13 | Last Amended: 2025-07-16 -->
<!-- Example: Version: 2.1.1 | Ratified: 2026-02-14 | Last Amended: 2026-02-15 -->
7 changes: 5 additions & 2 deletions .windsurf/rules/specify-rules.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand All @@ -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

<!-- MANUAL ADDITIONS START -->
<!-- MANUAL ADDITIONS END -->
3 changes: 2 additions & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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
Expand Down
24 changes: 24 additions & 0 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,30 @@
<meta name="description" content="Speed reading app." />
<title>Speed Reader</title>

<!-- Apply theme before React loads to prevent flash -->
<script>
(function () {
switch (localStorage.getItem('speedreader.theme')) {
case 'light':
break;
case 'dark':
document.documentElement.classList.add('dark');
break;
case 'system':
default:
if (window.matchMedia('(prefers-color-scheme: dark)').matches) {
document.documentElement.classList.add('dark');
}
}
})();
</script>
<style>
.dark body {
background-color: #111827; /* bg-slate-900 */
color: #f9fafb; /* text-slate-50 */
}
</style>

<!--
GitHub Corners. See https://github.com/remarkablemark/github-corners
-->
Expand Down
52 changes: 52 additions & 0 deletions specs/001-dark-mode/checklists/accessibility.md
Original file line number Diff line number Diff line change
@@ -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
34 changes: 34 additions & 0 deletions specs/001-dark-mode/checklists/requirements.md
Original file line number Diff line number Diff line change
@@ -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`
198 changes: 198 additions & 0 deletions specs/001-dark-mode/contracts/component-contracts.md
Original file line number Diff line number Diff line change
@@ -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;
}
```
Loading