From c42fb0e975620d711eea87e701a64aa6e3b306f1 Mon Sep 17 00:00:00 2001 From: Mark Date: Sat, 14 Feb 2026 22:38:55 -0500 Subject: [PATCH 01/25] docs(specs): specify component refactor --- .../checklists/requirements.md | 36 +++++ specs/001-component-refactor/spec.md | 134 ++++++++++++++++++ 2 files changed, 170 insertions(+) create mode 100644 specs/001-component-refactor/checklists/requirements.md create mode 100644 specs/001-component-refactor/spec.md diff --git a/specs/001-component-refactor/checklists/requirements.md b/specs/001-component-refactor/checklists/requirements.md new file mode 100644 index 0000000..baee4c1 --- /dev/null +++ b/specs/001-component-refactor/checklists/requirements.md @@ -0,0 +1,36 @@ +# Specification Quality Checklist: Component Refactoring + +**Purpose**: Validate specification completeness and quality before proceeding to planning +**Created**: 2026-02-14 +**Feature**: [Component Refactoring](./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 + +- Specification is complete and ready for planning phase +- All components are clearly defined with their responsibilities +- Success criteria are measurable and technology-agnostic diff --git a/specs/001-component-refactor/spec.md b/specs/001-component-refactor/spec.md new file mode 100644 index 0000000..9d0de7b --- /dev/null +++ b/specs/001-component-refactor/spec.md @@ -0,0 +1,134 @@ +# Feature Specification: Component Refactoring + +**Feature Branch**: `001-component-refactor` +**Created**: 2026-02-14 +**Status**: Draft +**Input**: User description: "refactor and modularize components" + +## User Scenarios & Testing _(mandatory)_ + +### User Story 1 - Extract Text Input Component (Priority: P1) + +As a developer, I want the text input functionality separated into its own component so that I can reuse it and test it independently. + +**Why this priority**: The text input is a core UI element that's currently tightly coupled with the main App component, making it hard to test and maintain. + +**Independent Test**: Can be fully tested by rendering the TextInput component in isolation and verifying text input, validation, and form submission behavior. + +**Acceptance Scenarios**: + +1. **Given** an empty text area, **When** user types text, **Then** the component updates its internal state and calls the onChange callback +2. **Given** invalid input (empty), **When** form is submitted, **Then** validation message is displayed and submission is prevented +3. **Given** valid input, **When** form is submitted, **Then** onSubmit callback is called with the text content + +--- + +### User Story 2 - Extract Reading Display Component (Priority: P1) + +As a developer, I want the reading display (current word display) separated into its own component so that I can style it independently and test its accessibility features. + +**Why this priority**: The reading display is the primary user-facing element during reading sessions and deserves focused testing and styling. + +**Independent Test**: Can be fully tested by rendering the ReadingDisplay component with different word states and verifying proper ARIA attributes and visual presentation. + +**Acceptance Scenarios**: + +1. **Given** a current word, **When** component renders, **Then** the word is displayed with proper typography and ARIA attributes +2. **Given** no current word, **When** component renders, **Then** appropriate empty state is shown +3. **Given** component is focused, **When** screen reader interacts, **Then** proper aria-live and aria-atomic attributes are present + +--- + +### User Story 3 - Extract Control Panel Component (Priority: P1) + +As a developer, I want the reading controls (speed slider, action buttons) separated into their own component so that I can manage control state independently and improve button organization. + +**Why this priority**: The control panel contains multiple interactive elements that have complex state-dependent behavior and need focused testing. + +**Independent Test**: Can be fully tested by rendering the ControlPanel component with different session states and verifying correct button visibility and behavior. + +**Acceptance Scenarios**: + +1. **Given** idle state, **When** component renders, **Then** only Start Reading button is enabled +2. **Given** running state, **When** component renders, **Then** Pause button is visible and functional +3. **Given** paused state, **When** component renders, **Then** Resume button is visible and functional +4. **Given** any state, **When** speed slider is adjusted, **Then** onSpeedChange callback is called with new value + +--- + +### User Story 4 - Extract Session Details Component (Priority: P2) + +As a developer, I want the session details (progress, tempo) separated into their own component so that I can format and display statistics independently. + +**Why this priority**: Session details are informational display elements that can be reused in different contexts and need focused styling. + +**Independent Test**: Can be fully tested by rendering the SessionDetails component with different session data and verifying proper formatting and display. + +**Acceptance Scenarios**: + +1. **Given** session progress data, **When** component renders, **Then** progress percentage and word counts are displayed correctly +2. **Given** tempo data, **When** component renders, **Then** WPM and ms/word values are calculated and displayed +3. **Given** collapsed state, **When** user clicks summary, **Then** details expand/collapse appropriately + +--- + +### User Story 5 - Extract Session Completion Component (Priority: P2) + +As a developer, I want the session completion message separated into its own component so that I can style completion states independently and add more completion features later. + +**Why this priority**: Completion messaging is a distinct user experience moment that may need enhanced features like sharing or restarting options. + +**Independent Test**: Can be fully tested by rendering the SessionCompletion component with completion data and verifying proper message display. + +**Acceptance Scenarios**: + +1. **Given** completed session data, **When** component renders, **Then** completion message with word count and timing is displayed +2. **Given** completion component, **When** displayed, **Then** appropriate success styling and semantic markup is applied + +--- + +### Edge Cases + +- What happens when components receive invalid props or missing data? +- How does system handle rapid state changes in reading session? +- What happens when text content is extremely long or contains special characters? +- How do components behave when accessibility features are enabled? + +## Requirements _(mandatory)_ + +### Constitution Alignment _(mandatory)_ + +- **Comprehension Outcome**: Component refactoring must preserve all existing reading functionality and user experience without breaking comprehension features. +- **Deterministic Behavior**: All extracted components must maintain the same state-driven behavior as the monolithic App component. +- **Accessibility Coverage**: Each component must maintain or improve existing ARIA attributes, keyboard navigation, and screen reader support. + +### Functional Requirements + +- **FR-001**: System MUST extract TextInput component from App.tsx with text input, validation, and form submission capabilities +- **FR-002**: System MUST extract ReadingDisplay component with current word display and proper ARIA attributes +- **FR-003**: System MUST extract ControlPanel component with speed slider and state-dependent action buttons +- **FR-004**: System MUST extract SessionDetails component with progress and tempo information display +- **FR-005**: System MUST extract SessionCompletion component with completion messaging and statistics +- **FR-006**: System MUST maintain all existing functionality and user interactions after refactoring +- **FR-007**: System MUST preserve all existing test coverage and add component-specific tests +- **FR-008**: Each component MUST have clear prop interfaces and TypeScript types +- **FR-009**: Components MUST follow established project patterns for file structure and naming +- **FR-010**: System MUST maintain responsive design and styling consistency across all components + +### Key Entities _(include if feature involves data)_ + +- **TextInput**: Handles text input, validation, and form submission for reading sessions +- **ReadingDisplay**: Displays current word with proper typography and accessibility +- **ControlPanel**: Manages speed control and reading session action buttons +- **SessionDetails**: Shows reading progress and tempo statistics +- **SessionCompletion**: Displays completion message and session summary + +## Success Criteria _(mandatory)_ + +### Measurable Outcomes + +- **SC-001**: All components can be rendered and tested independently with 100% test coverage +- **SC-002**: App.tsx component size reduced by at least 60% through component extraction +- **SC-003**: No existing functionality is lost - all user interactions work identically after refactoring +- **SC-004**: Component prop interfaces are clearly defined with TypeScript strict mode compliance +- **SC-005**: All components follow established file structure patterns with proper barrel exports From 18378d2956c1d57b80a2fd27a3d45f6de0542df3 Mon Sep 17 00:00:00 2001 From: Mark Date: Sat, 14 Feb 2026 22:43:43 -0500 Subject: [PATCH 02/25] docs(specs): clarify project structure --- specs/001-component-refactor/spec.md | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/specs/001-component-refactor/spec.md b/specs/001-component-refactor/spec.md index 9d0de7b..04fc3dd 100644 --- a/specs/001-component-refactor/spec.md +++ b/specs/001-component-refactor/spec.md @@ -112,8 +112,9 @@ As a developer, I want the session completion message separated into its own com - **FR-006**: System MUST maintain all existing functionality and user interactions after refactoring - **FR-007**: System MUST preserve all existing test coverage and add component-specific tests - **FR-008**: Each component MUST have clear prop interfaces and TypeScript types -- **FR-009**: Components MUST follow established project patterns for file structure and naming -- **FR-010**: System MUST maintain responsive design and styling consistency across all components +- **FR-009**: Components MUST follow established project patterns with individual folders containing component file, types, tests, and index.ts exports +- **FR-010**: Component-specific utilities MUST be colocated with their primary consuming component, shared types moved to `src/types/` +- **FR-011**: System MUST maintain responsive design and styling consistency across all components ### Key Entities _(include if feature involves data)_ @@ -123,6 +124,13 @@ As a developer, I want the session completion message separated into its own com - **SessionDetails**: Shows reading progress and tempo statistics - **SessionCompletion**: Displays completion message and session summary +## Clarifications + +### Session 2026-02-14 + +- Q: How should the new component directories be structured within the existing `src/components/` folder? → A: Each component in its own folder: `src/components/TextInput/`, `src/components/ReadingDisplay/`, etc. - each with component file, types, tests, and index.ts +- Q: Should utilities and shared types be colocated with components that use them, or organized in separate shared folders? → A: Colocate - Move hooks/types with primary component (e.g., `useReadingSession` with ControlPanel), shared types in `src/types/` + ## Success Criteria _(mandatory)_ ### Measurable Outcomes From c4df6efbd9847637d3887d7dbf0de1e312e84c58 Mon Sep 17 00:00:00 2001 From: Mark Date: Sat, 14 Feb 2026 22:45:35 -0500 Subject: [PATCH 03/25] docs(specs): clarify reusable components --- specs/001-component-refactor/spec.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/specs/001-component-refactor/spec.md b/specs/001-component-refactor/spec.md index 04fc3dd..d59be23 100644 --- a/specs/001-component-refactor/spec.md +++ b/specs/001-component-refactor/spec.md @@ -115,6 +115,7 @@ As a developer, I want the session completion message separated into its own com - **FR-009**: Components MUST follow established project patterns with individual folders containing component file, types, tests, and index.ts exports - **FR-010**: Component-specific utilities MUST be colocated with their primary consuming component, shared types moved to `src/types/` - **FR-011**: System MUST maintain responsive design and styling consistency across all components +- **FR-012**: System MUST extract Button component with primary/secondary variants to eliminate styling duplication ### Key Entities _(include if feature involves data)_ @@ -123,6 +124,7 @@ As a developer, I want the session completion message separated into its own com - **ControlPanel**: Manages speed control and reading session action buttons - **SessionDetails**: Shows reading progress and tempo statistics - **SessionCompletion**: Displays completion message and session summary +- **Button**: Reusable button component with primary/secondary variants for consistent styling ## Clarifications @@ -130,6 +132,7 @@ As a developer, I want the session completion message separated into its own com - Q: How should the new component directories be structured within the existing `src/components/` folder? → A: Each component in its own folder: `src/components/TextInput/`, `src/components/ReadingDisplay/`, etc. - each with component file, types, tests, and index.ts - Q: Should utilities and shared types be colocated with components that use them, or organized in separate shared folders? → A: Colocate - Move hooks/types with primary component (e.g., `useReadingSession` with ControlPanel), shared types in `src/types/` +- Q: Should we create shared button component variants or utility classes to eliminate the significant button styling duplication? → A: Create Button component with primary/secondary variants using component-level styling ## Success Criteria _(mandatory)_ From f3dcef9f148f7f4537a70ae2c256b0c01240235b Mon Sep 17 00:00:00 2001 From: Mark Date: Sat, 14 Feb 2026 22:50:28 -0500 Subject: [PATCH 04/25] docs(specs): create plan --- .../contracts/component-interfaces.md | 152 ++++++++++++ specs/001-component-refactor/data-model.md | 180 ++++++++++++++ specs/001-component-refactor/plan.md | 116 +++++++++ specs/001-component-refactor/quickstart.md | 223 ++++++++++++++++++ specs/001-component-refactor/research.md | 71 ++++++ 5 files changed, 742 insertions(+) create mode 100644 specs/001-component-refactor/contracts/component-interfaces.md create mode 100644 specs/001-component-refactor/data-model.md create mode 100644 specs/001-component-refactor/plan.md create mode 100644 specs/001-component-refactor/quickstart.md create mode 100644 specs/001-component-refactor/research.md diff --git a/specs/001-component-refactor/contracts/component-interfaces.md b/specs/001-component-refactor/contracts/component-interfaces.md new file mode 100644 index 0000000..a7cbb8f --- /dev/null +++ b/specs/001-component-refactor/contracts/component-interfaces.md @@ -0,0 +1,152 @@ +# Component Interface Contracts + +**Date**: 2026-02-14 +**Purpose**: Define component prop interfaces for extracted components + +## Button Component Contract + +```typescript +interface ButtonProps { + variant: 'primary' | 'secondary'; + children: React.ReactNode; + disabled?: boolean; + onClick?: () => void; + type?: 'button' | 'submit'; + className?: string; +} +``` + +**Behavior Contract**: + +- Primary variant renders with sky-600 background and white text +- Secondary variant renders with slate-300 border and white background +- Disabled state applies opacity-50 and cursor-not-allowed styles +- Responsive design reduces padding and text size on mobile (max-[480px]) +- All variants maintain focus-visible outline with sky-500 color + +## TextInput Component Contract + +```typescript +interface TextInputProps { + value: string; + onChange: (value: string) => void; + onSubmit: (text: string) => void; + isValid: boolean; + disabled?: boolean; +} +``` + +**Behavior Contract**: + +- Renders textarea with 10 rows and minimum height of 56 (14rem) +- Displays validation message when isValid is false +- Calls onChange callback on each input change +- Calls onSubmit callback when form is submitted with valid input +- Prevents form submission when input is invalid +- Applies focus styles with sky-500 border and ring + +## ReadingDisplay Component Contract + +```typescript +interface ReadingDisplayProps { + currentWord: string; + hasWords: boolean; +} +``` + +**Behavior Contract**: + +- Renders current word with 48px font size (3rem) on desktop +- Responsive design reduces to 32px (2rem) on mobile (max-[480px]) +- Minimum height of 160px (10rem) on desktop, 136px (8.5rem) on mobile +- Applies aria-live="polite" and aria-atomic="true" for screen readers +- Uses role="status" for proper semantic meaning +- Applies tracking-wide and font-semibold for readability + +## ControlPanel Component Contract + +```typescript +interface ControlPanelProps { + selectedWpm: number; + onSpeedChange: (wpm: number) => void; + onStartReading: () => void; + onPauseReading: () => void; + onResumeReading: () => void; + onRestartReading: () => void; + onEditText: () => void; + isInputValid: boolean; + status: ReadingSessionStatus; +} +``` + +**Behavior Contract**: + +- Renders speed slider with min/max values from readerConfig +- Calls onSpeedChange when slider value changes +- Conditionally renders action buttons based on status: + - idle: Start Reading button (primary) + - running: Pause button (secondary) + Restart + Edit Text + - paused: Resume button (primary) + Restart + Edit Text + - completed: Restart + Edit Text +- Start Reading button disabled when isInputValid is false +- All buttons maintain responsive design patterns + +## SessionDetails Component Contract + +```typescript +interface SessionDetailsProps { + wordsRead: number; + totalWords: number; + progressPercent: number; + selectedWpm: number; + msPerWord: number; +} +``` + +**Behavior Contract**: + +- Renders collapsible details with summary "Session details" +- Displays progress as "X / Y (Z%)" format +- Displays tempo as "WPM WPM (X ms/word)" format +- Uses aria-live="polite" for screen reader announcements +- Rounds percentage and ms/word values for display + +## SessionCompletion Component Contract + +```typescript +interface SessionCompletionProps { + wordsRead: number; + elapsedMs: number; +} +``` + +**Behavior Contract**: + +- Renders success message with emerald-200 border and emerald-50 background +- Displays "Session complete" heading with font-semibold +- Shows completion message with word count and elapsed time +- Uses semantic h2 heading for proper document structure + +## Shared Type Contracts + +### ReadingSessionStatus + +```typescript +type ReadingSessionStatus = 'idle' | 'running' | 'paused' | 'completed'; +``` + +### TokenizedContent + +```typescript +interface TokenizedContent { + totalWords: number; + words: string[]; +} +``` + +**Validation Rules**: + +- All props are required unless explicitly marked optional +- Component callbacks must be called with correct parameter types +- Status-dependent rendering must match the specified conditions +- Accessibility attributes must be present as specified diff --git a/specs/001-component-refactor/data-model.md b/specs/001-component-refactor/data-model.md new file mode 100644 index 0000000..bccc93a --- /dev/null +++ b/specs/001-component-refactor/data-model.md @@ -0,0 +1,180 @@ +# Data Model: Component Refactoring + +**Date**: 2026-02-14 +**Scope**: Component interfaces and shared types for extracted components + +## Component Interfaces + +### Button Component + +```typescript +interface ButtonProps { + variant: 'primary' | 'secondary'; + children: React.ReactNode; + disabled?: boolean; + onClick?: () => void; + type?: 'button' | 'submit'; + className?: string; +} +``` + +### TextInput Component + +```typescript +interface TextInputProps { + value: string; + onChange: (value: string) => void; + onSubmit: (text: string) => void; + isValid: boolean; + disabled?: boolean; +} +``` + +### ReadingDisplay Component + +```typescript +interface ReadingDisplayProps { + currentWord: string; + hasWords: boolean; +} +``` + +### ControlPanel Component + +```typescript +interface ControlPanelProps { + selectedWpm: number; + onSpeedChange: (wpm: number) => void; + onStartReading: () => void; + onPauseReading: () => void; + onResumeReading: () => void; + onRestartReading: () => void; + onEditText: () => void; + isInputValid: boolean; + status: ReadingSessionStatus; +} +``` + +### SessionDetails Component + +```typescript +interface SessionDetailsProps { + wordsRead: number; + totalWords: number; + progressPercent: number; + selectedWpm: number; + msPerWord: number; +} +``` + +### SessionCompletion Component + +```typescript +interface SessionCompletionProps { + wordsRead: number; + elapsedMs: number; +} +``` + +## Shared Types (src/types/readerTypes.ts) + +```typescript +// Reading session states +type ReadingSessionStatus = 'idle' | 'running' | 'paused' | 'completed'; + +// Reading session data +interface ReadingSessionState { + currentWordIndex: number; + elapsedMs: number; + msPerWord: number; + progressPercent: number; + restartCount: number; + selectedWpm: number; + startCount: number; + status: ReadingSessionStatus; + totalWords: number; + wordsRead: number; +} + +// Reading session actions +interface ReadingSessionActions { + editText: () => void; + pauseReading: () => void; + restartReading: () => void; + resumeReading: () => void; + setSelectedWpm: (wpm: number) => void; + startReading: (totalWords: number) => void; +} + +// Tokenized content +interface TokenizedContent { + totalWords: number; + words: string[]; +} + +// Reader configuration +interface ReaderConfig { + minWpm: number; + maxWpm: number; +} +``` + +## Component Relationships + +``` +App (Container) +├── TextInput (Form) +│ └── Button (primary variant) +├── ReadingDisplay (Display) +├── ControlPanel (Controls) +│ ├── Button (primary/secondary variants) +│ └── Speed Slider (input) +├── SessionDetails (Information) +└── SessionCompletion (Status) +``` + +## Data Flow + +1. **TextInput** → App → **useReadingSession** hook +2. **useReadingSession** → **ControlPanel** (actions) +3. **useReadingSession** → **ReadingDisplay** (current word) +4. **useReadingSession** → **SessionDetails** (statistics) +5. **useReadingSession** → **SessionCompletion** (completion state) + +## Validation Rules + +### TextInput + +- Must contain at least one word character +- Empty input is invalid +- Form submission prevented on invalid input + +### Button + +- Primary variant: sky-600 background, white text +- Secondary variant: slate-300 border, white background, slate-800 text +- Disabled state: opacity-50, cursor-not-allowed +- Responsive: smaller padding/text on mobile (max-[480px]) + +### ControlPanel + +- Start button only visible in idle state +- Pause button only visible in running state +- Resume button only visible in paused state +- Restart/Edit buttons visible in non-idle states + +## State Transitions + +``` +idle → running (startReading) +running → paused (pauseReading) +paused → running (resumeReading) +any state → idle (restartReading, editText) +running/paused → completed (natural progression) +``` + +## Performance Considerations + +- All components are pure functions with deterministic props +- No additional state management required beyond existing useReadingSession hook +- Component boundaries align with natural UI divisions for optimal React rendering diff --git a/specs/001-component-refactor/plan.md b/specs/001-component-refactor/plan.md new file mode 100644 index 0000000..5351792 --- /dev/null +++ b/specs/001-component-refactor/plan.md @@ -0,0 +1,116 @@ +# Implementation Plan: Component Refactoring + +**Branch**: `001-component-refactor` | **Date**: 2026-02-14 | **Spec**: [Component Refactoring](./spec.md) +**Input**: Feature specification from `/specs/001-component-refactor/spec.md` + +**Note**: This template is filled in by the `/speckit.plan` command. See `.specify/templates/commands/plan.md` for the execution workflow. + +## Summary + +Extract 6 components from the monolithic App.tsx to improve maintainability, testability, and follow DRY principles. Components: TextInput, ReadingDisplay, ControlPanel, SessionDetails, SessionCompletion, and Button (for styling deduplication). Each component will be organized in individual folders following project patterns with colocated utilities and shared types moved to src/types/. + +## Technical Context + +**Language/Version**: TypeScript 5 (strict mode) with React 19 +**Primary Dependencies**: React 19, Vite 7, Vitest 4, Tailwind CSS 4 +**Storage**: N/A (client-side state management) +**Testing**: Vitest 4 with @testing-library/react and @testing-library/user-event +**Target Platform**: Web browser (responsive design) +**Project Type**: Single React web application +**Performance Goals**: Maintain existing reading session performance, no timing regressions +**Constraints**: Must preserve all existing functionality and accessibility features +**Scale/Scope**: Refactor existing 224-line App.tsx into 6 focused components + +## Constitution Check + +_GATE: Must pass before Phase 0 research. Re-check after Phase 1 design._ + +- [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. + +## 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 # Simplified main component +│ │ ├── App.test.tsx # Integration tests +│ │ └── index.ts +│ ├── Button/ +│ │ ├── Button.tsx # Reusable button with variants +│ │ ├── Button.types.ts # Button prop interfaces +│ │ ├── Button.test.tsx # Unit tests +│ │ └── index.ts +│ ├── ControlPanel/ +│ │ ├── ControlPanel.tsx # Speed slider and action buttons +│ │ ├── ControlPanel.types.ts +│ │ ├── ControlPanel.test.tsx +│ │ ├── useReadingSession.ts # Colocated hook +│ │ └── index.ts +│ ├── ReadingDisplay/ +│ │ ├── ReadingDisplay.tsx # Current word display +│ │ ├── ReadingDisplay.types.ts +│ │ ├── ReadingDisplay.test.tsx +│ │ └── index.ts +│ ├── SessionDetails/ +│ │ ├── SessionDetails.tsx # Progress and tempo stats +│ │ ├── SessionDetails.types.ts +│ │ ├── SessionDetails.test.tsx +│ │ └── index.ts +│ ├── SessionCompletion/ +│ │ ├── SessionCompletion.tsx # Completion messaging +│ │ ├── SessionCompletion.types.ts +│ │ ├── SessionCompletion.test.tsx +│ │ └── index.ts +│ └── TextInput/ +│ ├── TextInput.tsx # Text input and validation +│ ├── TextInput.types.ts +│ ├── TextInput.test.tsx +│ ├── tokenizeContent.ts # Colocated utility +│ └── index.ts +├── types/ +│ ├── readerTypes.ts # Shared reading session types +│ └── index.ts +└── ... + +test/ +├── components/ +│ └── [component-specific integration tests] +└── setupFiles.ts +``` + +**Structure Decision**: Single React web application with component-based architecture. Each extracted component follows the established project pattern with individual folders containing component file, types, tests, and barrel exports. Component-specific utilities are colocated, while shared types are centralized in src/types/. + +## Complexity Tracking + +> **Fill ONLY if Constitution Check has violations that must be justified** + +| Violation | Why Needed | Simpler Alternative Rejected Because | +| -------------------------- | ------------------ | ------------------------------------ | +| [e.g., 4th project] | [current need] | [why 3 projects insufficient] | +| [e.g., Repository pattern] | [specific problem] | [why direct DB access insufficient] | diff --git a/specs/001-component-refactor/quickstart.md b/specs/001-component-refactor/quickstart.md new file mode 100644 index 0000000..399d157 --- /dev/null +++ b/specs/001-component-refactor/quickstart.md @@ -0,0 +1,223 @@ +# Quickstart Guide: Component Refactoring Implementation + +**Date**: 2026-02-14 +**Branch**: `001-component-refactor` + +## Overview + +This guide provides step-by-step instructions for implementing the component refactoring. The goal is to extract 6 components from the monolithic App.tsx while preserving all existing functionality and improving code maintainability. + +## Prerequisites + +- Ensure you're on the `001-component-refactor` branch +- Run `npm install` to ensure dependencies are up to date +- Verify existing tests pass: `npm run test:ci` + +## Implementation Order + +### Phase 1: Create Shared Infrastructure + +1. **Create Button Component** (Foundation) + + ```bash + mkdir -p src/components/Button + touch src/components/Button/{Button.tsx,Button.types.ts,Button.test.tsx,index.ts} + ``` + +2. **Create Shared Types** + ```bash + mkdir -p src/types + touch src/types/{readerTypes.ts,index.ts} + ``` + +### Phase 2: Extract Components (Dependency Order) + +3. **Extract TextInput Component** + - Move text input logic and validation + - Colocate tokenizeContent utility + - Test form submission and validation + +4. **Extract ReadingDisplay Component** + - Move current word display logic + - Preserve ARIA attributes and styling + - Test word display and accessibility + +5. **Extract Button Component Usage** + - Replace all inline button styles with Button component + - Verify primary/secondary variants + - Test responsive design and disabled states + +6. **Extract ControlPanel Component** + - Move speed slider and action buttons + - Colocate useReadingSession hook + - Test state-dependent button visibility + +7. **Extract SessionDetails Component** + - Move progress and tempo display + - Test collapsible behavior and calculations + +8. **Extract SessionCompletion Component** + - Move completion messaging + - Test success styling and timing display + +### Phase 3: Integration and Cleanup + +9. **Refactor App.tsx** + - Remove extracted code + - Import and use new components + - Verify all functionality preserved + +10. **Move Shared Types** + - Extract shared interfaces to src/types/ + - Update component imports + - Verify TypeScript compilation + +## Development Commands + +### During Implementation + +```bash +# Run tests after each component extraction +npm run test:ci + +# Type check after TypeScript changes +npm run lint:tsc + +# Lint code after styling changes +npm run lint + +# Start dev server for manual verification +npm start +``` + +### Final Verification + +```bash +# Full test suite with coverage +npm run test:ci + +# Type checking +npm run lint:tsc + +# Linting +npm run lint + +# Build verification +npm run build +``` + +## File Structure Reference + +``` +src/ +├── components/ +│ ├── App/ +│ │ ├── App.tsx # Simplified main component +│ │ ├── App.test.tsx # Integration tests +│ │ └── index.ts +│ ├── Button/ +│ │ ├── Button.tsx # Reusable button with variants +│ │ ├── Button.types.ts # Button prop interfaces +│ │ ├── Button.test.tsx # Unit tests +│ │ └── index.ts +│ ├── ControlPanel/ +│ │ ├── ControlPanel.tsx # Speed slider and action buttons +│ │ ├── ControlPanel.types.ts +│ │ ├── ControlPanel.test.tsx +│ │ ├── useReadingSession.ts # Colocated hook +│ │ └── index.ts +│ ├── ReadingDisplay/ +│ │ ├── ReadingDisplay.tsx # Current word display +│ │ ├── ReadingDisplay.types.ts +│ │ ├── ReadingDisplay.test.tsx +│ │ └── index.ts +│ ├── SessionDetails/ +│ │ ├── SessionDetails.tsx # Progress and tempo stats +│ │ ├── SessionDetails.types.ts +│ │ ├── SessionDetails.test.tsx +│ │ └── index.ts +│ ├── SessionCompletion/ +│ │ ├── SessionCompletion.tsx # Completion messaging +│ │ ├── SessionCompletion.types.ts +│ │ ├── SessionCompletion.test.tsx +│ │ └── index.ts +│ └── TextInput/ +│ ├── TextInput.tsx # Text input and validation +│ ├── TextInput.types.ts +│ ├── TextInput.test.tsx +│ ├── tokenizeContent.ts # Colocated utility +│ └── index.ts +├── types/ +│ ├── readerTypes.ts # Shared reading session types +│ └── index.ts +``` + +## Testing Strategy + +### Unit Tests + +- Each component has its own test file +- Test props, callbacks, and conditional rendering +- Verify accessibility attributes +- Test responsive design classes + +### Integration Tests + +- App.test.tsx verifies component integration +- Test full user workflows +- Verify state management through useReadingSession + +### Coverage Requirements + +- Maintain 100% test coverage +- All statements, branches, functions, and lines covered +- Use Vitest globals and Testing Library patterns + +## Success Criteria + +✅ **Component Extraction**: All 6 components extracted with proper interfaces +✅ **DRY Principle**: Button styling duplication eliminated +✅ **Functionality**: All existing features preserved +✅ **Accessibility**: ARIA attributes and keyboard navigation maintained +✅ **Testing**: 100% coverage maintained with component-specific tests +✅ **Code Quality**: TypeScript strict mode compliance, no lint errors +✅ **Performance**: No regressions in reading session timing + +## Troubleshooting + +### Common Issues + +- **Import errors**: Verify barrel exports in index.ts files +- **Type errors**: Check shared types are properly imported +- **Test failures**: Ensure mock implementations match new component interfaces +- **Styling issues**: Verify Tailwind classes are preserved in Button variants + +### Debug Commands + +```bash +# Check specific component tests +npm test -- src/components/Button/Button.test.tsx + +# Type check specific file +npx tsc --noEmit src/components/TextInput/TextInput.tsx + +# Lint specific component +npx eslint src/components/ControlPanel/ +``` + +## Next Steps + +After completing the refactoring: + +1. Run final verification commands +2. Update documentation if needed +3. Submit pull request with detailed description +4. Verify CI/CD pipeline passes +5. Merge to main branch + +## Resources + +- [Component Specification](./spec.md) +- [Data Model](./data-model.md) +- [Component Contracts](./contracts/component-interfaces.md) +- [Research Summary](./research.md) diff --git a/specs/001-component-refactor/research.md b/specs/001-component-refactor/research.md new file mode 100644 index 0000000..f799ce4 --- /dev/null +++ b/specs/001-component-refactor/research.md @@ -0,0 +1,71 @@ +# Research Summary: Component Refactoring + +**Date**: 2026-02-14 +**Scope**: Component extraction and DRY optimization for existing React application + +## Technology Decisions + +### React Component Architecture + +**Decision**: Use functional components with hooks following existing patterns +**Rationale**: Consistent with current codebase, React 19 best practices, and enables proper testing +**Alternatives considered**: Class components (rejected - inconsistent with codebase), custom hooks abstraction (rejected - over-engineering for this scope) + +### TypeScript Organization + +**Decision**: Colocate component-specific types, move shared types to `src/types/` +**Rationale**: Balances encapsulation with reusability, follows established React patterns +**Alternatives considered**: All types in central location (rejected - poor component cohesion), inline types (rejected - poor reusability) + +### Button Component Strategy + +**Decision**: Create reusable Button component with variant system using component-level styling +**Rationale**: Eliminates significant Tailwind class duplication, provides consistent styling, enables better maintainability +**Alternatives considered**: Tailwind utility classes (rejected - CSS file addition), @apply directive (rejected - less component-scoped) + +### Testing Strategy + +**Decision**: Maintain existing Vitest + Testing Library approach with component-specific test files +**Rationale**: Preserves current toolchain, enables isolated component testing, maintains coverage requirements +**Alternatives considered**: Storybook (rejected - out of scope), visual regression testing (rejected - over-engineering) + +## Performance Considerations + +### Component Rendering + +**Finding**: No performance impacts expected from component extraction +**Rationale**: React Compiler handles memoization automatically, component boundaries align with natural UI divisions + +### Bundle Size + +**Finding**: Neutral impact on bundle size +**Rationale**: Same amount of code, just reorganized; potential minor improvement from better tree-shaking + +## Accessibility Preservation + +**Finding**: All existing accessibility patterns will be preserved +**Rationale**: Component extraction maintains semantic HTML, ARIA attributes, and keyboard navigation patterns established in original code + +## Migration Strategy + +**Decision**: Incremental component extraction with continuous testing +**Rationale**: Minimizes risk, enables validation at each step, maintains working application throughout process +**Alternatives considered**: Big-bang refactoring (rejected - high risk, difficult to debug) + +## Risk Assessment + +**Low Risk Areas**: + +- Component extraction (well-defined boundaries) +- Button deduplication (clear duplication pattern) +- Type organization (standard React practice) + +**Mitigation Strategies**: + +- Preserve all existing tests during migration +- Run test suite after each component extraction +- Maintain visual regression testing through manual verification + +## Conclusion + +All technical decisions align with existing project patterns and React best practices. No external dependencies or new technologies required. The refactoring can proceed with minimal risk and high confidence in maintaining existing functionality. From 88f311405cf00eac106e5f28d63c28c575386f66 Mon Sep 17 00:00:00 2001 From: Mark Date: Sat, 14 Feb 2026 22:50:58 -0500 Subject: [PATCH 05/25] docs(windsurf): update agent context --- .windsurf/rules/specify-rules.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.windsurf/rules/specify-rules.md b/.windsurf/rules/specify-rules.md index 8b0d26b..b6eede7 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 (strict mode) with React 19 + React 19, Vite 7, Vitest 4, Tailwind CSS 4 (001-component-refactor) +- N/A (client-side state management) (001-component-refactor) + - TypeScript 5 (strict) with React 19 + React 19, React DOM 19, Vite 7, Tailwind CSS 4 (001-speed-reading-app) ## Project Structure @@ -24,6 +27,8 @@ TypeScript 5 (strict) with React 19: Follow standard conventions ## Recent Changes +- 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 From be7f5ac19168d7daf8f5fae61241cf9e0ba42230 Mon Sep 17 00:00:00 2001 From: Mark Date: Sat, 14 Feb 2026 22:52:17 -0500 Subject: [PATCH 06/25] docs(specs): correct test in plan --- specs/001-component-refactor/plan.md | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/specs/001-component-refactor/plan.md b/specs/001-component-refactor/plan.md index 5351792..28410a2 100644 --- a/specs/001-component-refactor/plan.md +++ b/specs/001-component-refactor/plan.md @@ -98,10 +98,7 @@ src/ │ └── index.ts └── ... -test/ -├── components/ -│ └── [component-specific integration tests] -└── setupFiles.ts +# Tests are colocated with components (see component folders above) ``` **Structure Decision**: Single React web application with component-based architecture. Each extracted component follows the established project pattern with individual folders containing component file, types, tests, and barrel exports. Component-specific utilities are colocated, while shared types are centralized in src/types/. From 42c99f1daa0291a5a9d07d4fcc992d88f025780e Mon Sep 17 00:00:00 2001 From: Mark Date: Sat, 14 Feb 2026 22:57:20 -0500 Subject: [PATCH 07/25] docs(specs): generate tasks --- specs/001-component-refactor/tasks.md | 285 ++++++++++++++++++++++++++ 1 file changed, 285 insertions(+) create mode 100644 specs/001-component-refactor/tasks.md diff --git a/specs/001-component-refactor/tasks.md b/specs/001-component-refactor/tasks.md new file mode 100644 index 0000000..dda6fe7 --- /dev/null +++ b/specs/001-component-refactor/tasks.md @@ -0,0 +1,285 @@ +--- +description: 'Task list for component refactoring implementation' +--- + +# Tasks: Component Refactoring + +**Input**: Design documents from `/specs/001-component-refactor/` +**Prerequisites**: plan.md (required), spec.md (required for user stories), research.md, data-model.md, contracts/ + +**Tests**: Include test tasks for behavior changes and bug fixes. Tests may be omitted only for +documentation-only or non-functional chores, and the omission MUST be justified in tasks.md. + +**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 + +- [ ] T001 Create component directory structure per implementation plan +- [ ] T002 [P] Create shared types directory in src/types/ +- [ ] T003 [P] Verify existing test setup and dependencies are current +- [ ] T004 [P] Establish accessibility and responsive test checklist for component refactoring + +--- + +## 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 + +- [ ] T005 Create Button component with primary/secondary variants in src/components/Button/ +- [ ] T006 [P] Create Button component types in src/components/Button/Button.types.ts +- [ ] T007 [P] Create Button component tests in src/components/Button/Button.test.tsx +- [ ] T008 Create Button component barrel export in src/components/Button/index.ts +- [ ] T009 Create shared reading session types in src/types/readerTypes.ts +- [ ] T010 Create shared types barrel export in src/types/index.ts + +**Checkpoint**: Foundation ready - user story implementation can now begin in parallel + +--- + +## Phase 3: User Story 1 - Extract Text Input Component (Priority: P1) 🎯 MVP + +**Goal**: Extract text input functionality into independent TextInput component with validation and form submission + +**Independent Test**: Render TextInput component in isolation and verify text input, validation, and form submission behavior + +### Tests for User Story 1 + +- [ ] T011 [P] [US1] Create TextInput component unit tests in src/components/TextInput/TextInput.test.tsx +- [ ] T012 [P] [US1] Create TextInput component integration tests in src/components/App/App.test.tsx + +### Implementation for User Story 1 + +- [ ] T013 [P] [US1] Create TextInput component types in src/components/TextInput/TextInput.types.ts +- [ ] T014 [P] [US1] Move tokenizeContent utility to src/components/TextInput/tokenizeContent.ts +- [ ] T015 [US1] Implement TextInput component in src/components/TextInput/TextInput.tsx +- [ ] T016 [US1] Create TextInput component barrel export in src/components/TextInput/index.ts +- [ ] T017 [US1] Update App.tsx to import and use TextInput component +- [ ] T018 [US1] Remove extracted text input code from App.tsx + +**Checkpoint**: At this point, User Story 1 should be fully functional and testable independently + +--- + +## Phase 4: User Story 2 - Extract Reading Display Component (Priority: P1) + +**Goal**: Extract current word display into independent ReadingDisplay component with proper accessibility + +**Independent Test**: Render ReadingDisplay component with different word states and verify proper ARIA attributes and visual presentation + +### Tests for User Story 2 + +- [ ] T019 [P] [US2] Create ReadingDisplay component unit tests in src/components/ReadingDisplay/ReadingDisplay.test.tsx +- [ ] T020 [P] [US2] Create ReadingDisplay component integration tests in src/components/App/App.test.tsx + +### Implementation for User Story 2 + +- [ ] T021 [P] [US2] Create ReadingDisplay component types in src/components/ReadingDisplay/ReadingDisplay.types.ts +- [ ] T022 [US2] Implement ReadingDisplay component in src/components/ReadingDisplay/ReadingDisplay.tsx +- [ ] T023 [US2] Create ReadingDisplay component barrel export in src/components/ReadingDisplay/index.ts +- [ ] T024 [US2] Update App.tsx to import and use ReadingDisplay component +- [ ] T025 [US2] Remove extracted reading display code from App.tsx + +**Checkpoint**: At this point, User Stories 1 AND 2 should both work independently + +--- + +## Phase 5: User Story 3 - Extract Control Panel Component (Priority: P1) + +**Goal**: Extract speed slider and action buttons into independent ControlPanel component with state-dependent behavior + +**Independent Test**: Render ControlPanel component with different session states and verify correct button visibility and behavior + +### Tests for User Story 3 + +- [ ] T026 [P] [US3] Create ControlPanel component unit tests in src/components/ControlPanel/ControlPanel.test.tsx +- [ ] T027 [P] [US3] Create ControlPanel component integration tests in src/components/App/App.test.tsx + +### Implementation for User Story 3 + +- [ ] T028 [P] [US3] Create ControlPanel component types in src/components/ControlPanel/ControlPanel.types.ts +- [ ] T029 [P] [US3] Move useReadingSession hook to src/components/ControlPanel/useReadingSession.ts +- [ ] T030 [US3] Implement ControlPanel component in src/components/ControlPanel/ControlPanel.tsx +- [ ] T031 [US3] Create ControlPanel component barrel export in src/components/ControlPanel/index.ts +- [ ] T032 [US3] Update App.tsx to import and use ControlPanel component +- [ ] T033 [US3] Remove extracted control panel code from App.tsx +- [ ] T034 [US3] Replace all Button usage in ControlPanel with Button component variants + +**Checkpoint**: At this point, User Stories 1, 2, AND 3 should all work independently + +--- + +## Phase 6: User Story 4 - Extract Session Details Component (Priority: P2) + +**Goal**: Extract progress and tempo statistics into independent SessionDetails component + +**Independent Test**: Render SessionDetails component with different session data and verify proper formatting and display + +### Tests for User Story 4 + +- [ ] T035 [P] [US4] Create SessionDetails component unit tests in src/components/SessionDetails/SessionDetails.test.tsx +- [ ] T036 [P] [US4] Create SessionDetails component integration tests in src/components/App/App.test.tsx + +### Implementation for User Story 4 + +- [ ] T037 [P] [US4] Create SessionDetails component types in src/components/SessionDetails/SessionDetails.types.ts +- [ ] T038 [US4] Implement SessionDetails component in src/components/SessionDetails/SessionDetails.tsx +- [ ] T039 [US4] Create SessionDetails component barrel export in src/components/SessionDetails/index.ts +- [ ] T040 [US4] Update App.tsx to import and use SessionDetails component +- [ ] T041 [US4] Remove extracted session details code from App.tsx + +**Checkpoint**: At this point, User Stories 1-4 should all work independently + +--- + +## Phase 7: User Story 5 - Extract Session Completion Component (Priority: P2) + +**Goal**: Extract completion messaging into independent SessionCompletion component + +**Independent Test**: Render SessionCompletion component with completion data and verify proper message display + +### Tests for User Story 5 + +- [ ] T042 [P] [US5] Create SessionCompletion component unit tests in src/components/SessionCompletion/SessionCompletion.test.tsx +- [ ] T043 [P] [US5] Create SessionCompletion component integration tests in src/components/App/App.test.tsx + +### Implementation for User Story 5 + +- [ ] T044 [P] [US5] Create SessionCompletion component types in src/components/SessionCompletion/SessionCompletion.types.ts +- [ ] T045 [US5] Implement SessionCompletion component in src/components/SessionCompletion/SessionCompletion.tsx +- [ ] T046 [US5] Create SessionCompletion component barrel export in src/components/SessionCompletion/index.ts +- [ ] T047 [US5] Update App.tsx to import and use SessionCompletion component +- [ ] T048 [US5] Remove extracted session completion code from App.tsx + +**Checkpoint**: All user stories should now be independently functional + +--- + +## Phase 8: Polish & Cross-Cutting Concerns + +**Purpose**: Improvements that affect multiple user stories + +- [ ] T049 [P] Update App.tsx imports to use new barrel exports +- [ ] T050 [P] Remove unused imports from App.tsx after component extraction +- [ ] T051 [P] Verify all components follow established file structure patterns +- [ ] T052 [P] Accessibility verification across keyboard, semantics, and responsive breakpoints +- [ ] T053 [P] Component integration tests in src/components/App/App.test.tsx for full workflow +- [ ] T054 [P] Additional regression tests for changed behavior +- [ ] T055 Code cleanup and refactoring of any remaining duplication +- [ ] T056 Performance verification - no timing regressions in reading sessions +- [ ] T057 Execute quality gates: `npm run lint`, `npm run lint:tsc`, `npm run test:ci` +- [ ] T058 Verify App.tsx size reduction meets 60% target +- [ ] T059 Manual verification of all user interactions work identically + +--- + +## 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-7)**: All depend on Foundational phase completion + - User stories can then proceed in parallel (if staffed) + - Or sequentially in priority order (P1 → P2) +- **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 (P1)**: Can start after Foundational (Phase 2) - May integrate with US1 but should be independently testable +- **User Story 3 (P1)**: Can start after Foundational (Phase 2) - May integrate with US1/US2 but should be independently testable +- **User Story 4 (P2)**: Can start after Foundational (Phase 2) - May integrate with US1-3 but should be independently testable +- **User Story 5 (P2)**: Can start after Foundational (Phase 2) - May integrate with US1-4 but should be independently testable + +### Within Each User Story + +- Tests MUST be written and FAIL before implementation +- Types before implementation +- Implementation 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 +- Types within a 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 + +```bash +# Launch all tests for User Story 1 together: +Task: "Create TextInput component unit tests in src/components/TextInput/TextInput.test.tsx" +Task: "Create TextInput component integration tests in src/components/App/App.test.tsx" + +# Launch all setup tasks for User Story 1 together: +Task: "Create TextInput component types in src/components/TextInput/TextInput.types.ts" +Task: "Move tokenizeContent utility to src/components/TextInput/tokenizeContent.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. Add User Story 4 → Test independently → Deploy/Demo +6. Add User Story 5 → Test independently → Deploy/Demo +7. 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 +- Each user story should be independently completable and testable +- Verify tests fail before implementing behavior changes +- Document justification for any omitted tests +- 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 From 1b7b2c16748a3509f1770d9d7a185e88bcc3a6a5 Mon Sep 17 00:00:00 2001 From: Mark Date: Sat, 14 Feb 2026 22:58:04 -0500 Subject: [PATCH 08/25] docs(specs): correct path conventions in tasks --- specs/001-component-refactor/tasks.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/specs/001-component-refactor/tasks.md b/specs/001-component-refactor/tasks.md index dda6fe7..fb763e0 100644 --- a/specs/001-component-refactor/tasks.md +++ b/specs/001-component-refactor/tasks.md @@ -20,10 +20,10 @@ documentation-only or non-functional chores, and the omission MUST be justified ## 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 +- **React static website**: `src/` at repository root +- Tests are colocated with components in their respective folders +- Shared types in `src/types/` +- All paths use this single-project structure ## Phase 1: Setup (Shared Infrastructure) From a2cea767b2cb3e7ce2b2e5cde3dc0bcf9ea7c2ab Mon Sep 17 00:00:00 2001 From: Mark Date: Sat, 14 Feb 2026 23:00:23 -0500 Subject: [PATCH 09/25] docs(specs): analyze and remediate critical issues --- specs/001-component-refactor/tasks.md | 114 +++++++++++++------------- 1 file changed, 58 insertions(+), 56 deletions(-) diff --git a/specs/001-component-refactor/tasks.md b/specs/001-component-refactor/tasks.md index fb763e0..8c59cf2 100644 --- a/specs/001-component-refactor/tasks.md +++ b/specs/001-component-refactor/tasks.md @@ -32,7 +32,8 @@ documentation-only or non-functional chores, and the omission MUST be justified - [ ] T001 Create component directory structure per implementation plan - [ ] T002 [P] Create shared types directory in src/types/ - [ ] T003 [P] Verify existing test setup and dependencies are current -- [ ] T004 [P] Establish accessibility and responsive test checklist for component refactoring +- [ ] T004 [P] Measure baseline App.tsx size for 60% reduction target verification +- [ ] T005 [P] Establish accessibility and responsive test checklist for component refactoring --- @@ -42,12 +43,12 @@ documentation-only or non-functional chores, and the omission MUST be justified **⚠️ CRITICAL**: No user story work can begin until this phase is complete -- [ ] T005 Create Button component with primary/secondary variants in src/components/Button/ -- [ ] T006 [P] Create Button component types in src/components/Button/Button.types.ts -- [ ] T007 [P] Create Button component tests in src/components/Button/Button.test.tsx -- [ ] T008 Create Button component barrel export in src/components/Button/index.ts -- [ ] T009 Create shared reading session types in src/types/readerTypes.ts -- [ ] T010 Create shared types barrel export in src/types/index.ts +- [ ] T006 Create Button component with primary/secondary variants in src/components/Button/ +- [ ] T007 [P] Create Button component types in src/components/Button/Button.types.ts +- [ ] T008 [P] Create Button component tests in src/components/Button/Button.test.tsx +- [ ] T009 Create Button component barrel export in src/components/Button/index.ts +- [ ] T010 Create shared reading session types in src/types/readerTypes.ts +- [ ] T011 Create shared types barrel export in src/types/index.ts **Checkpoint**: Foundation ready - user story implementation can now begin in parallel @@ -61,17 +62,17 @@ documentation-only or non-functional chores, and the omission MUST be justified ### Tests for User Story 1 -- [ ] T011 [P] [US1] Create TextInput component unit tests in src/components/TextInput/TextInput.test.tsx -- [ ] T012 [P] [US1] Create TextInput component integration tests in src/components/App/App.test.tsx +- [ ] T012 [P] [US1] Create TextInput component unit tests in src/components/TextInput/TextInput.test.tsx +- [ ] T013 [P] [US1] Create TextInput component integration tests in src/components/App/App.test.tsx ### Implementation for User Story 1 -- [ ] T013 [P] [US1] Create TextInput component types in src/components/TextInput/TextInput.types.ts -- [ ] T014 [P] [US1] Move tokenizeContent utility to src/components/TextInput/tokenizeContent.ts -- [ ] T015 [US1] Implement TextInput component in src/components/TextInput/TextInput.tsx -- [ ] T016 [US1] Create TextInput component barrel export in src/components/TextInput/index.ts -- [ ] T017 [US1] Update App.tsx to import and use TextInput component -- [ ] T018 [US1] Remove extracted text input code from App.tsx +- [ ] T014 [P] [US1] Create TextInput component types in src/components/TextInput/TextInput.types.ts +- [ ] T015 [P] [US1] Move tokenizeContent utility to src/components/TextInput/tokenizeContent.ts +- [ ] T016 [US1] Implement TextInput component in src/components/TextInput/TextInput.tsx +- [ ] T017 [US1] Create TextInput component barrel export in src/components/TextInput/index.ts +- [ ] T018 [US1] Update App.tsx to import and use TextInput component +- [ ] T019 [US1] Remove extracted text input code from App.tsx **Checkpoint**: At this point, User Story 1 should be fully functional and testable independently @@ -85,16 +86,16 @@ documentation-only or non-functional chores, and the omission MUST be justified ### Tests for User Story 2 -- [ ] T019 [P] [US2] Create ReadingDisplay component unit tests in src/components/ReadingDisplay/ReadingDisplay.test.tsx -- [ ] T020 [P] [US2] Create ReadingDisplay component integration tests in src/components/App/App.test.tsx +- [ ] T020 [P] [US2] Create ReadingDisplay component unit tests in src/components/ReadingDisplay/ReadingDisplay.test.tsx +- [ ] T021 [P] [US2] Create ReadingDisplay component integration tests in src/components/App/App.test.tsx ### Implementation for User Story 2 -- [ ] T021 [P] [US2] Create ReadingDisplay component types in src/components/ReadingDisplay/ReadingDisplay.types.ts -- [ ] T022 [US2] Implement ReadingDisplay component in src/components/ReadingDisplay/ReadingDisplay.tsx -- [ ] T023 [US2] Create ReadingDisplay component barrel export in src/components/ReadingDisplay/index.ts -- [ ] T024 [US2] Update App.tsx to import and use ReadingDisplay component -- [ ] T025 [US2] Remove extracted reading display code from App.tsx +- [ ] T022 [P] [US2] Create ReadingDisplay component types in src/components/ReadingDisplay/ReadingDisplay.types.ts +- [ ] T023 [US2] Implement ReadingDisplay component in src/components/ReadingDisplay/ReadingDisplay.tsx +- [ ] T024 [US2] Create ReadingDisplay component barrel export in src/components/ReadingDisplay/index.ts +- [ ] T025 [US2] Update App.tsx to import and use ReadingDisplay component +- [ ] T026 [US2] Remove extracted reading display code from App.tsx **Checkpoint**: At this point, User Stories 1 AND 2 should both work independently @@ -108,18 +109,18 @@ documentation-only or non-functional chores, and the omission MUST be justified ### Tests for User Story 3 -- [ ] T026 [P] [US3] Create ControlPanel component unit tests in src/components/ControlPanel/ControlPanel.test.tsx -- [ ] T027 [P] [US3] Create ControlPanel component integration tests in src/components/App/App.test.tsx +- [ ] T027 [P] [US3] Create ControlPanel component unit tests in src/components/ControlPanel/ControlPanel.test.tsx +- [ ] T028 [P] [US3] Create ControlPanel component integration tests in src/components/App/App.test.tsx ### Implementation for User Story 3 -- [ ] T028 [P] [US3] Create ControlPanel component types in src/components/ControlPanel/ControlPanel.types.ts -- [ ] T029 [P] [US3] Move useReadingSession hook to src/components/ControlPanel/useReadingSession.ts -- [ ] T030 [US3] Implement ControlPanel component in src/components/ControlPanel/ControlPanel.tsx -- [ ] T031 [US3] Create ControlPanel component barrel export in src/components/ControlPanel/index.ts -- [ ] T032 [US3] Update App.tsx to import and use ControlPanel component -- [ ] T033 [US3] Remove extracted control panel code from App.tsx -- [ ] T034 [US3] Replace all Button usage in ControlPanel with Button component variants +- [ ] T029 [P] [US3] Create ControlPanel component types in src/components/ControlPanel/ControlPanel.types.ts +- [ ] T030 [P] [US3] Move useReadingSession hook to src/components/ControlPanel/useReadingSession.ts +- [ ] T031 [US3] Implement ControlPanel component in src/components/ControlPanel/ControlPanel.tsx +- [ ] T032 [US3] Create ControlPanel component barrel export in src/components/ControlPanel/index.ts +- [ ] T033 [US3] Update App.tsx to import and use ControlPanel component +- [ ] T034 [US3] Remove extracted control panel code from App.tsx +- [ ] T035 [US3] Replace all Button usage in ControlPanel with Button component variants **Checkpoint**: At this point, User Stories 1, 2, AND 3 should all work independently @@ -133,16 +134,16 @@ documentation-only or non-functional chores, and the omission MUST be justified ### Tests for User Story 4 -- [ ] T035 [P] [US4] Create SessionDetails component unit tests in src/components/SessionDetails/SessionDetails.test.tsx -- [ ] T036 [P] [US4] Create SessionDetails component integration tests in src/components/App/App.test.tsx +- [ ] T036 [P] [US4] Create SessionDetails component unit tests in src/components/SessionDetails/SessionDetails.test.tsx +- [ ] T037 [P] [US4] Create SessionDetails component integration tests in src/components/App/App.test.tsx ### Implementation for User Story 4 -- [ ] T037 [P] [US4] Create SessionDetails component types in src/components/SessionDetails/SessionDetails.types.ts -- [ ] T038 [US4] Implement SessionDetails component in src/components/SessionDetails/SessionDetails.tsx -- [ ] T039 [US4] Create SessionDetails component barrel export in src/components/SessionDetails/index.ts -- [ ] T040 [US4] Update App.tsx to import and use SessionDetails component -- [ ] T041 [US4] Remove extracted session details code from App.tsx +- [ ] T038 [P] [US4] Create SessionDetails component types in src/components/SessionDetails/SessionDetails.types.ts +- [ ] T039 [US4] Implement SessionDetails component in src/components/SessionDetails/SessionDetails.tsx +- [ ] T040 [US4] Create SessionDetails component barrel export in src/components/SessionDetails/index.ts +- [ ] T041 [US4] Update App.tsx to import and use SessionDetails component +- [ ] T042 [US4] Remove extracted session details code from App.tsx **Checkpoint**: At this point, User Stories 1-4 should all work independently @@ -156,16 +157,16 @@ documentation-only or non-functional chores, and the omission MUST be justified ### Tests for User Story 5 -- [ ] T042 [P] [US5] Create SessionCompletion component unit tests in src/components/SessionCompletion/SessionCompletion.test.tsx -- [ ] T043 [P] [US5] Create SessionCompletion component integration tests in src/components/App/App.test.tsx +- [ ] T043 [P] [US5] Create SessionCompletion component unit tests in src/components/SessionCompletion/SessionCompletion.test.tsx +- [ ] T044 [P] [US5] Create SessionCompletion component integration tests in src/components/App/App.test.tsx ### Implementation for User Story 5 -- [ ] T044 [P] [US5] Create SessionCompletion component types in src/components/SessionCompletion/SessionCompletion.types.ts -- [ ] T045 [US5] Implement SessionCompletion component in src/components/SessionCompletion/SessionCompletion.tsx -- [ ] T046 [US5] Create SessionCompletion component barrel export in src/components/SessionCompletion/index.ts -- [ ] T047 [US5] Update App.tsx to import and use SessionCompletion component -- [ ] T048 [US5] Remove extracted session completion code from App.tsx +- [ ] T045 [P] [US5] Create SessionCompletion component types in src/components/SessionCompletion/SessionCompletion.types.ts +- [ ] T046 [US5] Implement SessionCompletion component in src/components/SessionCompletion/SessionCompletion.tsx +- [ ] T047 [US5] Create SessionCompletion component barrel export in src/components/SessionCompletion/index.ts +- [ ] T048 [US5] Update App.tsx to import and use SessionCompletion component +- [ ] T049 [US5] Remove extracted session completion code from App.tsx **Checkpoint**: All user stories should now be independently functional @@ -175,17 +176,18 @@ documentation-only or non-functional chores, and the omission MUST be justified **Purpose**: Improvements that affect multiple user stories -- [ ] T049 [P] Update App.tsx imports to use new barrel exports -- [ ] T050 [P] Remove unused imports from App.tsx after component extraction -- [ ] T051 [P] Verify all components follow established file structure patterns -- [ ] T052 [P] Accessibility verification across keyboard, semantics, and responsive breakpoints -- [ ] T053 [P] Component integration tests in src/components/App/App.test.tsx for full workflow -- [ ] T054 [P] Additional regression tests for changed behavior -- [ ] T055 Code cleanup and refactoring of any remaining duplication -- [ ] T056 Performance verification - no timing regressions in reading sessions -- [ ] T057 Execute quality gates: `npm run lint`, `npm run lint:tsc`, `npm run test:ci` -- [ ] T058 Verify App.tsx size reduction meets 60% target -- [ ] T059 Manual verification of all user interactions work identically +- [ ] T050 [P] Update App.tsx imports to use new barrel exports +- [ ] T051 [P] Remove unused imports from App.tsx after component extraction +- [ ] T052 [P] Verify all components follow established file structure patterns +- [ ] T053 [P] Accessibility verification across keyboard, semantics, and responsive breakpoints +- [ ] T054 [P] Component integration tests in src/components/App/App.test.tsx for full workflow +- [ ] T055 [P] Additional regression tests for changed behavior +- [ ] T056 Code cleanup and refactoring of any remaining duplication +- [ ] T057 Performance verification - no timing regressions in reading sessions +- [ ] T058 Execute quality gates: `npm run lint`, `npm run lint:tsc`, `npm run test:ci` +- [ ] T059 [P] Verify 100% test coverage is maintained after refactoring +- [ ] T060 Verify App.tsx size reduction meets 60% target +- [ ] T061 Manual verification of all user interactions work identically --- From 6c06b08ccdb1fdafea42bda8a98b744c5b2b4003 Mon Sep 17 00:00:00 2001 From: Mark Date: Sat, 14 Feb 2026 23:02:52 -0500 Subject: [PATCH 10/25] docs(AGENTS): ask before editing dot files --- AGENTS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/AGENTS.md b/AGENTS.md index a127c94..7c11c57 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -144,7 +144,7 @@ src/components/ComponentName/ ## Boundaries - ✅ **Always:** Write to `src/`; run lint, type check, and tests before commits; follow naming conventions -- ⚠️ **Ask first:** Adding dependencies, modifying CI/CD config, changing build configuration +- ⚠️ **Ask first:** Adding dependencies, modifying CI/CD config, changing build configuration, editing dot files - 🚫 **Never:** Commit secrets or API keys, edit `node_modules/`, disable ESLint rules, commit with failing tests ## Development Notes From b0b0b91ad38da6b6b2ce436c621e489b2dd9e91d Mon Sep 17 00:00:00 2001 From: Mark Date: Sat, 14 Feb 2026 23:10:16 -0500 Subject: [PATCH 11/25] chore(components): create Button and complete phase 1 and 2 --- .../checklists/accessibility.md | 97 +++++++++++++++++++ specs/001-component-refactor/tasks.md | 22 ++--- src/components/Button/Button.test.tsx | 85 ++++++++++++++++ src/components/Button/Button.tsx | 41 ++++++++ src/components/Button/Button.types.ts | 34 +++++++ src/components/Button/index.ts | 2 + src/types/index.ts | 7 ++ src/types/readerTypes.ts | 38 ++++++++ 8 files changed, 315 insertions(+), 11 deletions(-) create mode 100644 specs/001-component-refactor/checklists/accessibility.md create mode 100644 src/components/Button/Button.test.tsx create mode 100644 src/components/Button/Button.tsx create mode 100644 src/components/Button/Button.types.ts create mode 100644 src/components/Button/index.ts create mode 100644 src/types/index.ts create mode 100644 src/types/readerTypes.ts diff --git a/specs/001-component-refactor/checklists/accessibility.md b/specs/001-component-refactor/checklists/accessibility.md new file mode 100644 index 0000000..566df7e --- /dev/null +++ b/specs/001-component-refactor/checklists/accessibility.md @@ -0,0 +1,97 @@ +# Accessibility Checklist: Component Refactoring + +**Purpose**: Verify accessibility compliance across all extracted components +**Created**: 2026-02-14 +**Feature**: [Component Refactoring](./spec.md) + +## Keyboard Navigation + +- [ ] All interactive elements are keyboard accessible +- [ ] Tab order follows logical visual sequence +- [ ] Focus indicators are visible and meet contrast requirements +- [ ] No keyboard traps - all elements can be navigated away from + +## Screen Reader Support + +- [ ] All buttons have proper aria-labels or accessible text +- [ ] Form elements have associated labels and descriptions +- [ ] Dynamic content updates use aria-live regions appropriately +- [ ] Semantic HTML elements are used correctly (button, input, etc.) + +## ARIA Attributes + +- [ ] aria-live="polite" and aria-atomic="true" for reading display +- [ ] role="status" for current word display +- [ ] Proper aria-labels for speed slider and controls +- [ ] aria-expanded for collapsible session details +- [ ] aria-disabled for disabled buttons + +## Color Contrast + +- [ ] Text meets WCAG AA contrast ratios (4.5:1 normal, 3:1 large) +- [ ] Interactive elements have sufficient contrast in all states +- [ ] Focus indicators meet contrast requirements +- [ ] Error messages are distinguishable from normal text + +## Responsive Design + +- [ ] All components work on mobile breakpoints (max-[480px]) +- [ ] Touch targets meet minimum size requirements (44px) +- [ ] Text remains readable at smaller sizes +- [ ] No horizontal scroll on mobile devices + +## Component-Specific Checks + +### Button Component + +- [ ] Primary and secondary variants have distinct visual states +- [ ] Disabled state is properly communicated +- [ ] Focus styles are consistent across variants +- [ ] Responsive sizing works on mobile + +### TextInput Component + +- [ ] Textarea has proper label association +- [ ] Validation errors are announced to screen readers +- [ ] Form submission prevention works with keyboard +- [ ] Placeholder text does not replace label + +### ReadingDisplay Component + +- [ ] Current word is properly announced +- [ ] Large text remains readable on mobile +- [ ] Focus management is appropriate +- [ ] Empty state is handled gracefully + +### ControlPanel Component + +- [ ] Speed slider has accessible labels +- [ ] Button state changes are announced +- [ ] Grouping of controls is logical +- [ ] Mobile touch targets are adequate + +### SessionDetails Component + +- [ ] Collapsible details are keyboard accessible +- [ ] Summary text is descriptive +- [ ] Progress information is clearly communicated +- [ ] Expand/collapse state is announced + +### SessionCompletion Component + +- [ ] Success message is properly announced +- [ ] Completion status is semantically correct +- [ ] Visual styling doesn't interfere with readability + +## Testing Requirements + +- [ ] Manual keyboard navigation testing completed +- [ ] Screen reader testing completed (VoiceOver/NVDA/JAWS) +- [ ] Automated accessibility testing completed +- [ ] Mobile accessibility testing completed + +## Notes + +- All components must maintain existing accessibility features +- New components should improve upon current accessibility where possible +- Test with actual assistive technology, not just automated tools diff --git a/specs/001-component-refactor/tasks.md b/specs/001-component-refactor/tasks.md index 8c59cf2..0013460 100644 --- a/specs/001-component-refactor/tasks.md +++ b/specs/001-component-refactor/tasks.md @@ -29,11 +29,11 @@ documentation-only or non-functional chores, and the omission MUST be justified **Purpose**: Project initialization and basic structure -- [ ] T001 Create component directory structure per implementation plan -- [ ] T002 [P] Create shared types directory in src/types/ -- [ ] T003 [P] Verify existing test setup and dependencies are current -- [ ] T004 [P] Measure baseline App.tsx size for 60% reduction target verification -- [ ] T005 [P] Establish accessibility and responsive test checklist for component refactoring +- [x] T001 Create component directory structure per implementation plan +- [x] T002 [P] Create shared types directory in src/types/ +- [x] T003 [P] Verify existing test setup and dependencies are current +- [x] T004 [P] Measure baseline App.tsx size for 60% reduction target verification (baseline: 223 lines) +- [x] T005 [P] Establish accessibility and responsive test checklist for component refactoring --- @@ -43,12 +43,12 @@ documentation-only or non-functional chores, and the omission MUST be justified **⚠️ CRITICAL**: No user story work can begin until this phase is complete -- [ ] T006 Create Button component with primary/secondary variants in src/components/Button/ -- [ ] T007 [P] Create Button component types in src/components/Button/Button.types.ts -- [ ] T008 [P] Create Button component tests in src/components/Button/Button.test.tsx -- [ ] T009 Create Button component barrel export in src/components/Button/index.ts -- [ ] T010 Create shared reading session types in src/types/readerTypes.ts -- [ ] T011 Create shared types barrel export in src/types/index.ts +- [x] T006 Create Button component with primary/secondary variants in src/components/Button/ +- [x] T007 [P] Create Button component types in src/components/Button/Button.types.ts +- [x] T008 [P] Create Button component tests in src/components/Button/Button.test.tsx +- [x] T009 Create Button component barrel export in src/components/Button/index.ts +- [x] T010 Create shared reading session types in src/types/readerTypes.ts +- [x] T011 Create shared types barrel export in src/types/index.ts **Checkpoint**: Foundation ready - user story implementation can now begin in parallel diff --git a/src/components/Button/Button.test.tsx b/src/components/Button/Button.test.tsx new file mode 100644 index 0000000..728c561 --- /dev/null +++ b/src/components/Button/Button.test.tsx @@ -0,0 +1,85 @@ +import { render, screen } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +import { describe, expect, test } from 'vitest'; + +import { Button } from './Button'; + +describe('Button', () => { + test('renders primary button by default', () => { + render(); + const button = screen.getByRole('button', { name: 'Click me' }); + + expect(button).toBeInTheDocument(); + expect(button).toHaveClass('border-sky-600', 'bg-sky-600', 'text-white'); + }); + + test('renders secondary button variant', () => { + render(); + const button = screen.getByRole('button', { name: 'Click me' }); + + expect(button).toHaveClass( + 'border-slate-300', + 'bg-white', + 'text-slate-800', + ); + }); + + test('handles click events', async () => { + const handleClick = vi.fn(); + const user = userEvent.setup(); + + render(); + const button = screen.getByRole('button', { name: 'Click me' }); + + await user.click(button); + + expect(handleClick).toHaveBeenCalledTimes(1); + }); + + test('can be disabled', () => { + render(); + const button = screen.getByRole('button', { name: 'Click me' }); + + expect(button).toBeDisabled(); + expect(button).toHaveClass( + 'disabled:cursor-not-allowed', + 'disabled:opacity-50', + ); + }); + + test('renders as submit button', () => { + render(); + const button = screen.getByRole('button', { name: 'Submit' }); + + expect(button).toHaveAttribute('type', 'submit'); + }); + + test('applies custom className', () => { + render(); + const button = screen.getByRole('button', { name: 'Click me' }); + + expect(button).toHaveClass('custom-class'); + }); + + test('has proper focus styles', () => { + render(); + const button = screen.getByRole('button', { name: 'Click me' }); + + expect(button).toHaveClass( + 'focus-visible:outline-2', + 'focus-visible:outline-offset-2', + 'focus-visible:outline-sky-500', + ); + }); + + test('has responsive design classes', () => { + render(); + const button = screen.getByRole('button', { name: 'Click me' }); + + expect(button).toHaveClass( + 'max-[480px]:px-[0.6rem]', + 'max-[480px]:py-[0.45rem]', + 'max-[480px]:text-[0.8rem]', + ); + }); +}); diff --git a/src/components/Button/Button.tsx b/src/components/Button/Button.tsx new file mode 100644 index 0000000..935e29d --- /dev/null +++ b/src/components/Button/Button.tsx @@ -0,0 +1,41 @@ +import type { ButtonProps } from './Button.types'; + +/** + * Reusable Button component with primary and secondary variants. + * Provides consistent styling and accessibility across the application. + */ +export const Button = ({ + variant = 'primary', + children, + disabled = false, + onClick, + type = 'button', + className = '', + ref, + ...props +}: ButtonProps & { ref?: React.Ref }) => { + const baseClasses = + 'inline-flex shrink-0 items-center justify-center rounded-md border px-3 py-2 text-sm font-medium transition focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-sky-500 disabled:cursor-not-allowed disabled:opacity-50 max-[480px]:px-[0.6rem] max-[480px]:py-[0.45rem] max-[480px]:text-[0.8rem]'; + + const variantClasses: Record<'primary' | 'secondary', string> = { + primary: + 'border-sky-600 bg-sky-600 text-white hover:border-sky-700 hover:bg-sky-700', + secondary: + 'border-slate-300 bg-white text-slate-800 hover:border-slate-400 hover:bg-slate-50', + }; + + const classes = `${baseClasses} ${variantClasses[variant]} ${className}`; + + return ( + + ); +}; diff --git a/src/components/Button/Button.types.ts b/src/components/Button/Button.types.ts new file mode 100644 index 0000000..7aeded3 --- /dev/null +++ b/src/components/Button/Button.types.ts @@ -0,0 +1,34 @@ +import type { ButtonHTMLAttributes } from 'react'; + +export interface ButtonProps extends Omit< + ButtonHTMLAttributes, + 'type' +> { + /** + * Visual style variant for the button + * @defaultValue 'primary' + */ + variant?: 'primary' | 'secondary'; + + /** + * Whether the button is disabled + * @defaultValue false + */ + disabled?: boolean; + + /** + * Click handler function + */ + onClick?: () => void; + + /** + * Button type attribute + * @defaultValue 'button' + */ + type?: 'button' | 'submit'; + + /** + * Additional CSS classes + */ + className?: string; +} diff --git a/src/components/Button/index.ts b/src/components/Button/index.ts new file mode 100644 index 0000000..b6cafa5 --- /dev/null +++ b/src/components/Button/index.ts @@ -0,0 +1,2 @@ +export { Button } from './Button'; +export type { ButtonProps } from './Button.types'; diff --git a/src/types/index.ts b/src/types/index.ts new file mode 100644 index 0000000..244777f --- /dev/null +++ b/src/types/index.ts @@ -0,0 +1,7 @@ +export type { + ReaderConfig, + ReadingSessionActions, + ReadingSessionState, + ReadingSessionStatus, + TokenizedContent, +} from './readerTypes'; diff --git a/src/types/readerTypes.ts b/src/types/readerTypes.ts new file mode 100644 index 0000000..711e55e --- /dev/null +++ b/src/types/readerTypes.ts @@ -0,0 +1,38 @@ +// Reading session states +export type ReadingSessionStatus = 'idle' | 'running' | 'paused' | 'completed'; + +// Reading session data +export interface ReadingSessionState { + currentWordIndex: number; + elapsedMs: number; + msPerWord: number; + progressPercent: number; + restartCount: number; + selectedWpm: number; + startCount: number; + status: ReadingSessionStatus; + totalWords: number; + wordsRead: number; +} + +// Reading session actions +export interface ReadingSessionActions { + editText: () => void; + pauseReading: () => void; + restartReading: () => void; + resumeReading: () => void; + setSelectedWpm: (wpm: number) => void; + startReading: (totalWords: number) => void; +} + +// Tokenized content +export interface TokenizedContent { + totalWords: number; + words: string[]; +} + +// Reader configuration +export interface ReaderConfig { + minWpm: number; + maxWpm: number; +} From 965491116f0fedd59780961167e95ebd410e39a8 Mon Sep 17 00:00:00 2001 From: Mark Date: Sat, 14 Feb 2026 23:26:47 -0500 Subject: [PATCH 12/25] refactor(components): integrate TextInput in App --- specs/001-component-refactor/tasks.md | 16 +- src/components/App/App.test.tsx | 60 +++++- src/components/App/App.tsx | 193 +++++++++----------- src/components/TextInput/TextInput.test.tsx | 156 ++++++++++++++++ src/components/TextInput/TextInput.tsx | 63 +++++++ src/components/TextInput/TextInput.types.ts | 27 +++ src/components/TextInput/index.ts | 4 + src/components/TextInput/tokenizeContent.ts | 35 ++++ 8 files changed, 428 insertions(+), 126 deletions(-) create mode 100644 src/components/TextInput/TextInput.test.tsx create mode 100644 src/components/TextInput/TextInput.tsx create mode 100644 src/components/TextInput/TextInput.types.ts create mode 100644 src/components/TextInput/index.ts create mode 100644 src/components/TextInput/tokenizeContent.ts diff --git a/specs/001-component-refactor/tasks.md b/specs/001-component-refactor/tasks.md index 0013460..546ff10 100644 --- a/specs/001-component-refactor/tasks.md +++ b/specs/001-component-refactor/tasks.md @@ -62,17 +62,17 @@ documentation-only or non-functional chores, and the omission MUST be justified ### Tests for User Story 1 -- [ ] T012 [P] [US1] Create TextInput component unit tests in src/components/TextInput/TextInput.test.tsx -- [ ] T013 [P] [US1] Create TextInput component integration tests in src/components/App/App.test.tsx +- [x] T012 [P] [US1] Create TextInput component unit tests in src/components/TextInput/TextInput.test.tsx +- [x] T013 [P] [US1] Create TextInput component integration tests in src/components/App/App.test.tsx ### Implementation for User Story 1 -- [ ] T014 [P] [US1] Create TextInput component types in src/components/TextInput/TextInput.types.ts -- [ ] T015 [P] [US1] Move tokenizeContent utility to src/components/TextInput/tokenizeContent.ts -- [ ] T016 [US1] Implement TextInput component in src/components/TextInput/TextInput.tsx -- [ ] T017 [US1] Create TextInput component barrel export in src/components/TextInput/index.ts -- [ ] T018 [US1] Update App.tsx to import and use TextInput component -- [ ] T019 [US1] Remove extracted text input code from App.tsx +- [x] T014 [P] [US1] Create TextInput component types in src/components/TextInput/TextInput.types.ts +- [x] T015 [P] [US1] Move tokenizeContent utility to src/components/TextInput/tokenizeContent.ts +- [x] T016 [US1] Implement TextInput component in src/components/TextInput/TextInput.tsx +- [x] T017 [US1] Create TextInput component barrel export in src/components/TextInput/index.ts +- [x] T018 [US1] Update App.tsx to import and use TextInput component +- [x] T019 [US1] Remove extracted text input code from App.tsx **Checkpoint**: At this point, User Story 1 should be fully functional and testable independently diff --git a/src/components/App/App.test.tsx b/src/components/App/App.test.tsx index 4615143..74fe48b 100644 --- a/src/components/App/App.test.tsx +++ b/src/components/App/App.test.tsx @@ -20,6 +20,52 @@ describe('App component', () => { expect(button).toBeDisabled(); }); + describe('TextInput integration', () => { + it('enables start button when text is entered', async () => { + const user = userEvent.setup(); + render(); + + const textarea = screen.getByLabelText(/session text/i); + const startButton = screen.getByRole('button', { + name: /start reading/i, + }); + + expect(startButton).toBeDisabled(); + + await user.type(textarea, 'Some valid text content'); + expect(startButton).toBeEnabled(); + }); + + it('shows validation error when submitting empty text', async () => { + const user = userEvent.setup(); + render(); + + const submitButton = screen.getByTestId('submit-button'); + + await user.click(submitButton); + + const errorMessage = screen.getByText( + /enter at least one word before starting/i, + ); + expect(errorMessage).toBeInTheDocument(); + }); + + it('submits form when valid text is entered', async () => { + const user = userEvent.setup(); + render(); + + const textarea = screen.getByLabelText(/session text/i); + const submitButton = screen.getByTestId('submit-button'); + + await user.type(textarea, 'Valid text content'); + await user.click(submitButton); + + // Should transition to reading mode + expect(screen.queryByLabelText(/session text/i)).not.toBeInTheDocument(); + expect(screen.getByRole('status')).toBeInTheDocument(); + }); + }); + it('enables start button after entering readable text', async () => { const user = userEvent.setup(); render(); @@ -64,19 +110,13 @@ describe('App component', () => { it('keeps setup mode on submit when text is invalid', () => { render(); - const startButton = screen.getByRole('button', { name: /start reading/i }); - const form = startButton.closest('form'); - - expect(form).not.toBeNull(); - if (form === null) { - return; - } + const submitButton = screen.getByTestId('submit-button'); - fireEvent.submit(form); + fireEvent.click(submitButton); expect(screen.getByLabelText(/session text/i)).toBeInTheDocument(); expect( - screen.getByRole('button', { name: /start reading/i }), - ).toBeDisabled(); + screen.getByText(/enter at least one word before starting/i), + ).toBeInTheDocument(); }); }); diff --git a/src/components/App/App.tsx b/src/components/App/App.tsx index a3a8941..3d89ffe 100644 --- a/src/components/App/App.tsx +++ b/src/components/App/App.tsx @@ -1,14 +1,12 @@ -import { type ChangeEvent, type SyntheticEvent, useId, useState } from 'react'; +import { type ChangeEvent, useId, useState } from 'react'; +import { hasReadableText, TextInput, tokenizeContent } from '../TextInput'; import { READER_MAX_WPM, READER_MIN_WPM } from './readerConfig'; -import { hasReadableText, tokenizeContent } from './tokenizeContent'; import { useReadingSession } from './useReadingSession'; export default function App() { const [rawText, setRawText] = useState(''); - const textAreaId = useId(); const speedInputId = useId(); - const validationId = useId(); const { currentWordIndex, @@ -29,7 +27,7 @@ export default function App() { startReading, } = useReadingSession(); - const { totalWords, words } = tokenizeContent(rawText); + const { words } = tokenizeContent(rawText); const isInputValid = hasReadableText(rawText); const isSetupMode = status === 'idle'; const isRunning = status === 'running'; @@ -38,20 +36,15 @@ export default function App() { const hasSessionWords = sessionWordCount > 0; const currentWord = hasSessionWords ? (words[currentWordIndex] ?? '') : ''; - const handleStartReading = (event: SyntheticEvent) => { - event.preventDefault(); - + const handleStartReading = (text: string) => { if (!isInputValid) { return; } + const { totalWords } = tokenizeContent(text); startReading(totalWords); }; - const handleTextChange = (event: ChangeEvent) => { - setRawText(event.target.value); - }; - const handleWpmChange = (event: ChangeEvent) => { setSelectedWpm(Number.parseInt(event.target.value, 10)); }; @@ -68,113 +61,97 @@ export default function App() {
-
- {isSetupMode ? ( -
- -