From fcfaa15c5f7d01d2bc83b9902d081f499d8e2ebf Mon Sep 17 00:00:00 2001 From: FORGE Date: Fri, 10 Apr 2026 00:16:00 +0000 Subject: [PATCH 1/7] feat: Create index.html and App entry point Run: c6b60c93-bf32-41d4-a111-87879d73c789 Task: fe356700-3b61-476b-a58e-0113b1157147 Agent: builder --- ARCHITECTURE.md | 132 ++++++++++++++++++++++++++++++++++++++++++++++ RUNNING.md | 47 +++++++++++------ index.html | 12 +++++ src/App.tsx | 17 ++++++ src/main.tsx | 20 +++++++ src/vite-env.d.ts | 1 + 6 files changed, 214 insertions(+), 15 deletions(-) create mode 100644 ARCHITECTURE.md create mode 100644 index.html create mode 100644 src/App.tsx create mode 100644 src/main.tsx create mode 100644 src/vite-env.d.ts diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md new file mode 100644 index 0000000..4b60f09 --- /dev/null +++ b/ARCHITECTURE.md @@ -0,0 +1,132 @@ +# Architecture — Mini React Todo App + +## Overview + +A client-side Todo application built with React, TypeScript, and Vite. The app allows users to add, toggle, and delete todo items. All state is managed in-memory via React's `useState` hook — no backend or persistence layer is included. + +## Type Definitions + +The core data type is defined in `src/types/Todo.ts`: + +```typescript +export interface Todo { + id: string; + text: string; + completed: boolean; +} +``` + +- **id** (`string`): A unique identifier generated via `crypto.randomUUID()` to avoid collisions. +- **text** (`string`): The user-visible description of the todo item. +- **completed** (`boolean`): Whether the todo has been marked as done. + +## Component Tree + +``` +App +├── TodoInput +└── TodoList + └── TodoItem (one per todo) +``` + +- `App` is the root component that owns all state. +- `TodoInput` captures new todo text from the user. +- `TodoList` renders the list of `TodoItem` components. +- `TodoItem` renders a single todo with toggle and delete controls. + +## Component Responsibilities + +### App + +- Owns the todo state via `useState([])`. +- Defines three mutation functions: + - `addTodo(text: string): void` — creates a new `Todo` with `crypto.randomUUID()` and prepends it to the list. Ignores empty/whitespace-only input. + - `toggleTodo(id: string): void` — flips the `completed` flag of the matching todo. + - `deleteTodo(id: string): void` — removes the todo with the given id from the list. +- Passes `addTodo` to `TodoInput` and `todos`, `toggleTodo`, `deleteTodo` to `TodoList`. + +### TodoInput + +- Maintains local state for the input field value via `useState('')`. +- On form submission, calls `props.onAdd(text)` if the trimmed text is non-empty, then clears the input. +- Prevents adding empty or whitespace-only todos. + +**Props interface:** +```typescript +interface TodoInputProps { + onAdd: (text: string) => void; +} +``` + +### TodoList + +- Receives the full `todos` array plus callback props. +- Maps over `todos` to render a `TodoItem` for each entry. +- Handles the empty-list case gracefully (renders nothing or a subtle message). + +**Props interface:** +```typescript +interface TodoListProps { + todos: Todo[]; + onToggle: (id: string) => void; + onDelete: (id: string) => void; +} +``` + +### TodoItem + +- Renders a single todo's text, a checkbox/toggle for completion, and a delete button. +- Applies a visual style (e.g. line-through) when `completed` is true. + +**Props interface:** +```typescript +interface TodoItemProps { + todo: Todo; + onToggle: (id: string) => void; + onDelete: (id: string) => void; +} +``` + +## Data Flow + +The application follows a strict **unidirectional data flow** (props-down, callbacks-up): + +1. `App` holds the single source of truth: `todos: Todo[]`. +2. State is passed **down** as props to child components. +3. User interactions in child components invoke callback props (`onAdd`, `onToggle`, `onDelete`) that call functions defined in `App`. +4. Those functions update state via `setTodos`, triggering a re-render that flows new props downward. + +No child component directly mutates the todo list. + +## State Management + +State management uses **only React `useState`**. No external state management libraries (Redux, Zustand, MobX, Jotai, etc.) are used. The single `useState` in `App` is sufficient for this application's complexity. + +## File Structure + +``` +/ +├── index.html # HTML shell with root div and script tag +├── package.json # Project dependencies and scripts +├── tsconfig.json # TypeScript configuration +├── vite.config.ts # Vite build configuration +├── ARCHITECTURE.md # This file +├── RUNNING.md # Setup and run instructions +└── src/ + ├── main.tsx # ReactDOM.createRoot entry point + ├── App.tsx # Root component with state and logic + ├── vite-env.d.ts # Vite client type references + ├── types/ + │ └── Todo.ts # Todo interface definition + └── components/ + ├── TodoInput.tsx # New-todo input form component + ├── TodoList.tsx # List container component + └── TodoItem.tsx # Single todo row component +``` + +## Edge Cases + +- **Empty todo list**: `TodoList` handles `todos.length === 0` gracefully by rendering an empty container or a placeholder message. +- **Empty/whitespace input**: `TodoInput` trims input and prevents submission of empty or whitespace-only strings. +- **ID collisions**: IDs are generated using `crypto.randomUUID()` which provides sufficient uniqueness. +- **No persistence**: All data is in-memory only. Refreshing the page resets the todo list. No `localStorage` or backend persistence is included in the base plan. diff --git a/RUNNING.md b/RUNNING.md index 77896cf..6db5065 100644 --- a/RUNNING.md +++ b/RUNNING.md @@ -1,33 +1,50 @@ -# Running the Todo API +# Running the Mini React Todo App + +## TEAM_BRIEF +stack: TypeScript/React+Vite +test_runner: pytest tests/ +lint_tool: none +coverage_tool: none +coverage_threshold: 0 +coverage_applies: false ## Prerequisites -- Python 3.10 or later +- Node.js 18+ and npm +- (Optional) Docker and Docker Compose -## Install dependencies +## Local Development (npm) ```bash -pip install fastapi uvicorn pydantic -``` +# Install dependencies +npm install -For running the test suite you will also need: - -```bash -pip install httpx pytest +# Start the Vite dev server +npm run dev ``` -## Start the server +Open [http://localhost:5173](http://localhost:5173) in your browser. + +## Docker ```bash -uvicorn main:app --reload --host 0.0.0.0 --port 8000 +# Build and start +docker compose up --build + +# Or run in background +docker compose up --build -d ``` -The API will be available at . +Open [http://localhost:5173](http://localhost:5173) in your browser. -Interactive docs are served at . +## Authentication -## Run the tests +No authentication is required. The app runs entirely client-side with no backend. + +## Build for Production ```bash -pytest tests/ +npm run build ``` + +Output is placed in the `dist/` directory and can be served by any static file server. diff --git a/index.html b/index.html new file mode 100644 index 0000000..c92cebd --- /dev/null +++ b/index.html @@ -0,0 +1,12 @@ + + + + + + Todo App + + +
+ + + diff --git a/src/App.tsx b/src/App.tsx new file mode 100644 index 0000000..6a271d5 --- /dev/null +++ b/src/App.tsx @@ -0,0 +1,17 @@ +import React from 'react'; + +/** + * Root application component. + * + * Renders the top-level heading for the Todo App. + * Child components (TodoInput, TodoList) will be added in subsequent tasks. + */ +function App(): React.JSX.Element { + return ( +
+

Todo App

+
+ ); +} + +export default App; diff --git a/src/main.tsx b/src/main.tsx new file mode 100644 index 0000000..9ecb6ff --- /dev/null +++ b/src/main.tsx @@ -0,0 +1,20 @@ +import React from 'react'; +import ReactDOM from 'react-dom/client'; +import App from './App'; + +/** + * Application entry point. + * + * Mounts the root component into the DOM element with id "root". + */ +const rootElement = document.getElementById('root'); + +if (!rootElement) { + throw new Error('Root element not found. Ensure index.html contains a
.'); +} + +ReactDOM.createRoot(rootElement).render( + + + , +); diff --git a/src/vite-env.d.ts b/src/vite-env.d.ts new file mode 100644 index 0000000..11f02fe --- /dev/null +++ b/src/vite-env.d.ts @@ -0,0 +1 @@ +/// From e9bc5195614b17fc2263bdb49df183c928987596 Mon Sep 17 00:00:00 2001 From: FORGE Date: Fri, 10 Apr 2026 00:16:44 +0000 Subject: [PATCH 2/7] feat: Define Todo type and TodoInput component Run: c6b60c93-bf32-41d4-a111-87879d73c789 Task: 9e232797-e55d-4b4f-92a1-e43a9fc00e08 Agent: builder --- ARCHITECTURE.md | 141 ++++++++++++++--------------------- RUNNING.md | 18 ++--- src/components/TodoInput.tsx | 46 ++++++++++++ src/types.ts | 15 ++++ 4 files changed, 122 insertions(+), 98 deletions(-) create mode 100644 src/components/TodoInput.tsx create mode 100644 src/types.ts diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index 4b60f09..c28b73e 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -1,25 +1,19 @@ # Architecture — Mini React Todo App -## Overview - -A client-side Todo application built with React, TypeScript, and Vite. The app allows users to add, toggle, and delete todo items. All state is managed in-memory via React's `useState` hook — no backend or persistence layer is included. +A client-side Todo application built with React, TypeScript, and Vite. ## Type Definitions -The core data type is defined in `src/types/Todo.ts`: +The core data model is defined in `src/types.ts`: ```typescript export interface Todo { - id: string; - text: string; - completed: boolean; + id: string; // Unique identifier (generated via crypto.randomUUID()) + text: string; // The text content of the todo + completed: boolean; // Whether the todo has been completed } ``` -- **id** (`string`): A unique identifier generated via `crypto.randomUUID()` to avoid collisions. -- **text** (`string`): The user-visible description of the todo item. -- **completed** (`boolean`): Whether the todo has been marked as done. - ## Component Tree ``` @@ -29,104 +23,81 @@ App └── TodoItem (one per todo) ``` -- `App` is the root component that owns all state. -- `TodoInput` captures new todo text from the user. -- `TodoList` renders the list of `TodoItem` components. -- `TodoItem` renders a single todo with toggle and delete controls. +- **App** is the root component that owns all state. +- **TodoInput** is a sibling of **TodoList**, both direct children of **App**. +- **TodoItem** is rendered by **TodoList** for each todo in the array. ## Component Responsibilities -### App +### App (`src/App.tsx`) -- Owns the todo state via `useState([])`. -- Defines three mutation functions: - - `addTodo(text: string): void` — creates a new `Todo` with `crypto.randomUUID()` and prepends it to the list. Ignores empty/whitespace-only input. - - `toggleTodo(id: string): void` — flips the `completed` flag of the matching todo. - - `deleteTodo(id: string): void` — removes the todo with the given id from the list. -- Passes `addTodo` to `TodoInput` and `todos`, `toggleTodo`, `deleteTodo` to `TodoList`. +- Owns the todo state via `useState`. +- Defines `addTodo`, `toggleTodo`, and `deleteTodo` functions. +- Passes `onAdd` callback to `TodoInput`. +- Passes the `todos` array, `onToggle`, and `onDelete` callbacks to `TodoList`. -### TodoInput +### TodoInput (`src/components/TodoInput.tsx`) -- Maintains local state for the input field value via `useState('')`. -- On form submission, calls `props.onAdd(text)` if the trimmed text is non-empty, then clears the input. -- Prevents adding empty or whitespace-only todos. +- Controlled text input inside a `
`. +- Calls `onAdd(text: string)` on form submission. +- Trims input and prevents adding empty or whitespace-only todos. +- Clears the input field after successful submission. -**Props interface:** -```typescript -interface TodoInputProps { - onAdd: (text: string) => void; -} -``` +### TodoList (`src/components/TodoList.tsx`) -### TodoList +- Receives `todos: Todo[]`, `onToggle: (id: string) => void`, and `onDelete: (id: string) => void` as props. +- Maps over the `todos` array and renders a `TodoItem` for each entry. +- Handles the empty-list case gracefully (renders nothing or an informational message). -- Receives the full `todos` array plus callback props. -- Maps over `todos` to render a `TodoItem` for each entry. -- Handles the empty-list case gracefully (renders nothing or a subtle message). +### TodoItem (`src/components/TodoItem.tsx`) -**Props interface:** -```typescript -interface TodoListProps { - todos: Todo[]; - onToggle: (id: string) => void; - onDelete: (id: string) => void; -} -``` +- Receives a single `todo: Todo`, `onToggle: (id: string) => void`, and `onDelete: (id: string) => void` as props. +- Displays the todo text with visual indication of completion status. +- Provides a toggle control (checkbox) and a delete button. + +## Data Flow -### TodoItem +The application follows a strict **unidirectional data flow** pattern: -- Renders a single todo's text, a checkbox/toggle for completion, and a delete button. -- Applies a visual style (e.g. line-through) when `completed` is true. +1. **Props down**: `App` passes data (the `todos` array) and callbacks (`onAdd`, `onToggle`, `onDelete`) to child components via props. +2. **Callbacks up**: Child components invoke the callback props to signal user actions. The callbacks update state in `App`, which triggers a re-render. -**Props interface:** -```typescript -interface TodoItemProps { - todo: Todo; - onToggle: (id: string) => void; - onDelete: (id: string) => void; -} -``` +No child component directly mutates the todo state. -## Data Flow +## State Management -The application follows a strict **unidirectional data flow** (props-down, callbacks-up): +All application state is managed using React's built-in `useState` hook inside the `App` component. **No external state management libraries** (Redux, Zustand, Jotai, etc.) are used. -1. `App` holds the single source of truth: `todos: Todo[]`. -2. State is passed **down** as props to child components. -3. User interactions in child components invoke callback props (`onAdd`, `onToggle`, `onDelete`) that call functions defined in `App`. -4. Those functions update state via `setTodos`, triggering a re-render that flows new props downward. +State shape: -No child component directly mutates the todo list. +```typescript +const [todos, setTodos] = useState([]); +``` -## State Management +Functions defined in App: -State management uses **only React `useState`**. No external state management libraries (Redux, Zustand, MobX, Jotai, etc.) are used. The single `useState` in `App` is sufficient for this application's complexity. +- `addTodo(text: string)`: Creates a new `Todo` with `id` from `crypto.randomUUID()`, the given `text`, and `completed: false`. Prepends it to the array. +- `toggleTodo(id: string)`: Toggles the `completed` field of the todo matching the given `id`. +- `deleteTodo(id: string)`: Removes the todo matching the given `id` from the array. ## File Structure ``` -/ -├── index.html # HTML shell with root div and script tag -├── package.json # Project dependencies and scripts -├── tsconfig.json # TypeScript configuration -├── vite.config.ts # Vite build configuration -├── ARCHITECTURE.md # This file -├── RUNNING.md # Setup and run instructions -└── src/ - ├── main.tsx # ReactDOM.createRoot entry point - ├── App.tsx # Root component with state and logic - ├── vite-env.d.ts # Vite client type references - ├── types/ - │ └── Todo.ts # Todo interface definition - └── components/ - ├── TodoInput.tsx # New-todo input form component - ├── TodoList.tsx # List container component - └── TodoItem.tsx # Single todo row component +src/ +├── types.ts # Todo interface definition +├── App.tsx # Root component with state management +├── App.css # App-level styles +├── main.tsx # Vite entry point, renders +├── index.css # Global styles +└── components/ + ├── TodoInput.tsx # Controlled input for adding todos + ├── TodoList.tsx # Renders list of TodoItem components + └── TodoItem.tsx # Single todo display with toggle/delete ``` ## Edge Cases -- **Empty todo list**: `TodoList` handles `todos.length === 0` gracefully by rendering an empty container or a placeholder message. -- **Empty/whitespace input**: `TodoInput` trims input and prevents submission of empty or whitespace-only strings. -- **ID collisions**: IDs are generated using `crypto.randomUUID()` which provides sufficient uniqueness. -- **No persistence**: All data is in-memory only. Refreshing the page resets the todo list. No `localStorage` or backend persistence is included in the base plan. +- **Empty todo list**: `TodoList` handles `todos.length === 0` gracefully. +- **Empty/whitespace input**: `TodoInput` trims input and prevents submission of empty strings. +- **ID collisions**: IDs are generated with `crypto.randomUUID()` which provides sufficient uniqueness. +- **No persistence**: There is no localStorage or server-side persistence in the base implementation. Data resets on page refresh. diff --git a/RUNNING.md b/RUNNING.md index 6db5065..e4a9713 100644 --- a/RUNNING.md +++ b/RUNNING.md @@ -1,4 +1,4 @@ -# Running the Mini React Todo App +# Running the Application ## TEAM_BRIEF stack: TypeScript/React+Vite @@ -11,15 +11,12 @@ coverage_applies: false ## Prerequisites - Node.js 18+ and npm -- (Optional) Docker and Docker Compose +- Docker and Docker Compose (optional, for containerised development) -## Local Development (npm) +## Local Development ```bash -# Install dependencies npm install - -# Start the Vite dev server npm run dev ``` @@ -28,23 +25,18 @@ Open [http://localhost:5173](http://localhost:5173) in your browser. ## Docker ```bash -# Build and start docker compose up --build - -# Or run in background -docker compose up --build -d ``` Open [http://localhost:5173](http://localhost:5173) in your browser. ## Authentication -No authentication is required. The app runs entirely client-side with no backend. +No authentication is required. The application is a client-side-only todo app. ## Build for Production ```bash npm run build +npm run preview ``` - -Output is placed in the `dist/` directory and can be served by any static file server. diff --git a/src/components/TodoInput.tsx b/src/components/TodoInput.tsx new file mode 100644 index 0000000..5b3be79 --- /dev/null +++ b/src/components/TodoInput.tsx @@ -0,0 +1,46 @@ +import React, { useState } from "react"; + +/** + * Props for the TodoInput component. + */ +interface TodoInputProps { + /** Callback invoked with the trimmed text when a new todo is submitted. */ + onAdd: (text: string) => void; +} + +/** + * A controlled input with a form that allows adding new todo items. + * + * - Calls `onAdd(text)` on form submission with the trimmed input value. + * - Clears the input field after successful submission. + * - Prevents adding empty or whitespace-only todos. + */ +const TodoInput: React.FC = ({ onAdd }) => { + const [text, setText] = useState(""); + + const handleSubmit = (e: React.FormEvent): void => { + e.preventDefault(); + const trimmed = text.trim(); + if (trimmed.length === 0) { + return; + } + onAdd(trimmed); + setText(""); + }; + + return ( + + ) => + setText(e.target.value) + } + placeholder="Add a new todo" + /> + + + ); +}; + +export default TodoInput; diff --git a/src/types.ts b/src/types.ts new file mode 100644 index 0000000..719a364 --- /dev/null +++ b/src/types.ts @@ -0,0 +1,15 @@ +/** + * Core type definitions for the Todo application. + */ + +/** + * Represents a single todo item in the application. + */ +export interface Todo { + /** Unique identifier for the todo item. */ + id: string; + /** The text content of the todo item. */ + text: string; + /** Whether the todo item has been completed. */ + completed: boolean; +} From 81455c76edc97cd1b92d00add9bf926d6edaec12 Mon Sep 17 00:00:00 2001 From: FORGE Date: Fri, 10 Apr 2026 00:17:32 +0000 Subject: [PATCH 3/7] feat: Create TodoItem component Run: c6b60c93-bf32-41d4-a111-87879d73c789 Task: c1a1b271-dc65-4911-a8d2-fe3ab1fe60e8 Agent: builder --- ARCHITECTURE.md | 114 ++++++++++++++++++++---------------- RUNNING.md | 21 ++++--- src/components/TodoItem.tsx | 57 ++++++++++++++++++ src/types/Todo.ts | 11 ++++ 4 files changed, 143 insertions(+), 60 deletions(-) create mode 100644 src/components/TodoItem.tsx create mode 100644 src/types/Todo.ts diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index c28b73e..f9facbf 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -1,16 +1,20 @@ -# Architecture — Mini React Todo App +# Mini React Todo App — Architecture -A client-side Todo application built with React, TypeScript, and Vite. +## Overview + +A lightweight todo application built with React, TypeScript, and Vite. The app +follows a unidirectional data-flow pattern with all state owned by the root +`App` component and passed down via props. ## Type Definitions -The core data model is defined in `src/types.ts`: +The core data type lives in `src/types/Todo.ts`: ```typescript export interface Todo { id: string; // Unique identifier (generated via crypto.randomUUID()) - text: string; // The text content of the todo - completed: boolean; // Whether the todo has been completed + text: string; // The todo item text + completed: boolean; // Whether the item is done } ``` @@ -20,84 +24,92 @@ export interface Todo { App ├── TodoInput └── TodoList - └── TodoItem (one per todo) + └── TodoItem (one per todo) ``` -- **App** is the root component that owns all state. -- **TodoInput** is a sibling of **TodoList**, both direct children of **App**. -- **TodoItem** is rendered by **TodoList** for each todo in the array. +- **App** is the root component that owns the `Todo[]` state. +- **TodoInput** captures new todo text from the user. +- **TodoList** receives the array of todos and renders a `TodoItem` for each. +- **TodoItem** renders a single todo with toggle and delete controls. ## Component Responsibilities ### App (`src/App.tsx`) -- Owns the todo state via `useState`. -- Defines `addTodo`, `toggleTodo`, and `deleteTodo` functions. -- Passes `onAdd` callback to `TodoInput`. -- Passes the `todos` array, `onToggle`, and `onDelete` callbacks to `TodoList`. +- Owns application state via `useState`. +- Defines three handler functions: + - `addTodo(text: string)` — creates a new `Todo` with `crypto.randomUUID()`, + prepends it to the array. + - `toggleTodo(id: string)` — flips the `completed` flag of the matching todo. + - `deleteTodo(id: string)` — removes the todo with the given id from state. +- Passes `addTodo` to `TodoInput`, and `todos`, `toggleTodo`, `deleteTodo` to + `TodoList`. ### TodoInput (`src/components/TodoInput.tsx`) -- Controlled text input inside a `
`. -- Calls `onAdd(text: string)` on form submission. -- Trims input and prevents adding empty or whitespace-only todos. -- Clears the input field after successful submission. +- Renders a text input and an "Add" button. +- Maintains local `useState` for the input value. +- On submit, trims the input; if non-empty, calls `onAdd(text)` and clears + the field. Empty or whitespace-only input is rejected (no-op). ### TodoList (`src/components/TodoList.tsx`) -- Receives `todos: Todo[]`, `onToggle: (id: string) => void`, and `onDelete: (id: string) => void` as props. -- Maps over the `todos` array and renders a `TodoItem` for each entry. -- Handles the empty-list case gracefully (renders nothing or an informational message). +- Receives `todos: Todo[]`, `onToggle`, and `onDelete` as props. +- Maps over `todos` rendering a `TodoItem` for each, keyed by `todo.id`. +- When `todos.length === 0`, renders a friendly empty-state message. ### TodoItem (`src/components/TodoItem.tsx`) -- Receives a single `todo: Todo`, `onToggle: (id: string) => void`, and `onDelete: (id: string) => void` as props. -- Displays the todo text with visual indication of completion status. -- Provides a toggle control (checkbox) and a delete button. +- Receives a single `todo: Todo`, `onToggle(id: string)`, and + `onDelete(id: string)` as props. +- Renders a checkbox bound to `todo.completed` that calls `onToggle(todo.id)` + on change. +- Renders `todo.text` with `text-decoration: line-through` when `completed` + is `true`. +- Renders a "Delete" button that calls `onDelete(todo.id)` on click. +- Pure presentational — contains no internal state. ## Data Flow -The application follows a strict **unidirectional data flow** pattern: +The app follows the standard React unidirectional data-flow pattern: -1. **Props down**: `App` passes data (the `todos` array) and callbacks (`onAdd`, `onToggle`, `onDelete`) to child components via props. -2. **Callbacks up**: Child components invoke the callback props to signal user actions. The callbacks update state in `App`, which triggers a re-render. +1. **Props down** — `App` passes the `todos` array and callback functions down + through `TodoList` and `TodoInput`. +2. **Callbacks up** — Child components invoke callbacks (`onAdd`, `onToggle`, + `onDelete`) to request state changes. +3. **State update** — `App` updates its `useState` hook, triggering a re-render + that flows new props back down the tree. -No child component directly mutates the todo state. +No events are emitted sideways between siblings; all communication goes through +the parent. ## State Management -All application state is managed using React's built-in `useState` hook inside the `App` component. **No external state management libraries** (Redux, Zustand, Jotai, etc.) are used. - -State shape: - -```typescript -const [todos, setTodos] = useState([]); -``` - -Functions defined in App: - -- `addTodo(text: string)`: Creates a new `Todo` with `id` from `crypto.randomUUID()`, the given `text`, and `completed: false`. Prepends it to the array. -- `toggleTodo(id: string)`: Toggles the `completed` field of the todo matching the given `id`. -- `deleteTodo(id: string)`: Removes the todo matching the given `id` from the array. +The application uses **only React `useState`** for state management. No external +state libraries (Redux, Zustand, MobX, etc.) are used. The single +`useState` in `App` is the sole source of truth. ## File Structure ``` src/ -├── types.ts # Todo interface definition -├── App.tsx # Root component with state management -├── App.css # App-level styles +├── App.tsx # Root component, state owner ├── main.tsx # Vite entry point, renders -├── index.css # Global styles +├── types/ +│ └── Todo.ts # Todo interface definition └── components/ - ├── TodoInput.tsx # Controlled input for adding todos - ├── TodoList.tsx # Renders list of TodoItem components - └── TodoItem.tsx # Single todo display with toggle/delete + ├── TodoInput.tsx # New-todo input form + ├── TodoList.tsx # List container + └── TodoItem.tsx # Single todo row ``` ## Edge Cases -- **Empty todo list**: `TodoList` handles `todos.length === 0` gracefully. -- **Empty/whitespace input**: `TodoInput` trims input and prevents submission of empty strings. -- **ID collisions**: IDs are generated with `crypto.randomUUID()` which provides sufficient uniqueness. -- **No persistence**: There is no localStorage or server-side persistence in the base implementation. Data resets on page refresh. +- **Empty list** — `TodoList` gracefully renders an empty-state message when + `todos` has length 0. +- **Blank input** — `TodoInput` prevents adding empty or whitespace-only todos. +- **ID collisions** — IDs are generated with `crypto.randomUUID()`, which + provides sufficient uniqueness for a client-side app. +- **No persistence** — Todos live only in React state; refreshing the page + clears all data. No `localStorage` or backend persistence is included in + the base plan. diff --git a/RUNNING.md b/RUNNING.md index e4a9713..6110080 100644 --- a/RUNNING.md +++ b/RUNNING.md @@ -10,17 +10,17 @@ coverage_applies: false ## Prerequisites -- Node.js 18+ and npm -- Docker and Docker Compose (optional, for containerised development) +- Node.js >= 18 +- npm >= 9 (or Docker + Docker Compose) -## Local Development +## Local Development (npm) ```bash npm install npm run dev ``` -Open [http://localhost:5173](http://localhost:5173) in your browser. +Open in your browser. ## Docker @@ -28,15 +28,18 @@ Open [http://localhost:5173](http://localhost:5173) in your browser. docker compose up --build ``` -Open [http://localhost:5173](http://localhost:5173) in your browser. +Open in your browser. ## Authentication -No authentication is required. The application is a client-side-only todo app. +No authentication is required. The app runs entirely in the browser with no +backend dependencies. -## Build for Production +## Running Tests ```bash -npm run build -npm run preview +pytest tests/ ``` + +Tests validate component structure and file presence. No browser or Node.js +runtime is required for the test suite. diff --git a/src/components/TodoItem.tsx b/src/components/TodoItem.tsx new file mode 100644 index 0000000..630ea0c --- /dev/null +++ b/src/components/TodoItem.tsx @@ -0,0 +1,57 @@ +import React from "react"; +import { Todo } from "../types/Todo"; + +/** + * Props for the TodoItem component. + */ +export interface TodoItemProps { + /** The todo object to render. */ + todo: Todo; + /** Callback invoked with the todo's id when the checkbox is toggled. */ + onToggle: (id: string) => void; + /** Callback invoked with the todo's id when the delete button is clicked. */ + onDelete: (id: string) => void; +} + +/** + * Presentational component that renders a single todo item. + * + * Displays the todo text with a checkbox for toggling completion status + * and a delete button for removing the item. When the todo is completed, + * the text is rendered with a line-through style. + */ +const TodoItem: React.FC = ({ todo, onToggle, onDelete }) => { + return ( +
  • + onToggle(todo.id)} + aria-label={`Toggle ${todo.text}`} + /> + + {todo.text} + + +
  • + ); +}; + +export default TodoItem; diff --git a/src/types/Todo.ts b/src/types/Todo.ts new file mode 100644 index 0000000..ff1f590 --- /dev/null +++ b/src/types/Todo.ts @@ -0,0 +1,11 @@ +/** + * Core Todo item interface used throughout the application. + */ +export interface Todo { + /** Unique identifier for the todo item. */ + id: string; + /** The text content of the todo item. */ + text: string; + /** Whether the todo item has been completed. */ + completed: boolean; +} From 12ab823ab0550ebb399c9b7cc116e7d50a95935b Mon Sep 17 00:00:00 2001 From: FORGE Date: Fri, 10 Apr 2026 00:18:20 +0000 Subject: [PATCH 4/7] feat: Create TodoList component Run: c6b60c93-bf32-41d4-a111-87879d73c789 Task: 85e560cb-e962-4328-9f2a-ab7591214608 Agent: builder --- ARCHITECTURE.md | 109 +++++++++++++++++------------------- RUNNING.md | 30 +++++----- src/components/TodoItem.tsx | 40 +++++-------- src/components/TodoList.tsx | 42 ++++++++++++++ src/types/Todo.ts | 6 +- 5 files changed, 124 insertions(+), 103 deletions(-) create mode 100644 src/components/TodoList.tsx diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index f9facbf..a869415 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -2,19 +2,18 @@ ## Overview -A lightweight todo application built with React, TypeScript, and Vite. The app -follows a unidirectional data-flow pattern with all state owned by the root -`App` component and passed down via props. +A lightweight, client-side todo application built with **React 18**, **TypeScript**, and **Vite**. All state lives in-memory inside the React component tree — there is no backend, no database, and no `localStorage` persistence in the base implementation. ## Type Definitions -The core data type lives in `src/types/Todo.ts`: +A single shared type is used across all components: ```typescript +// src/types/Todo.ts export interface Todo { - id: string; // Unique identifier (generated via crypto.randomUUID()) - text: string; // The todo item text - completed: boolean; // Whether the item is done + id: string; // generated via crypto.randomUUID() + text: string; // user-supplied todo text + completed: boolean; // toggled by the user } ``` @@ -24,70 +23,56 @@ export interface Todo { App ├── TodoInput └── TodoList - └── TodoItem (one per todo) + └── TodoItem (rendered once per todo) ``` -- **App** is the root component that owns the `Todo[]` state. -- **TodoInput** captures new todo text from the user. -- **TodoList** receives the array of todos and renders a `TodoItem` for each. -- **TodoItem** renders a single todo with toggle and delete controls. +- **App** is the root component that owns all state. +- **TodoInput** is a sibling of **TodoList**, both are direct children of **App**. +- **TodoItem** is rendered by **TodoList** for every item in the `todos` array. ## Component Responsibilities ### App (`src/App.tsx`) -- Owns application state via `useState`. -- Defines three handler functions: - - `addTodo(text: string)` — creates a new `Todo` with `crypto.randomUUID()`, - prepends it to the array. - - `toggleTodo(id: string)` — flips the `completed` flag of the matching todo. - - `deleteTodo(id: string)` — removes the todo with the given id from state. -- Passes `addTodo` to `TodoInput`, and `todos`, `toggleTodo`, `deleteTodo` to - `TodoList`. +- Owns the single piece of application state: `const [todos, setTodos] = useState([]);` +- Defines three mutator functions: + - `addTodo(text: string): void` — creates a new `Todo` with `crypto.randomUUID()`, prepends it to state. + - `toggleTodo(id: string): void` — flips the `completed` flag of the matching todo. + - `deleteTodo(id: string): void` — removes the matching todo from state. +- Renders `` and ``. ### TodoInput (`src/components/TodoInput.tsx`) -- Renders a text input and an "Add" button. -- Maintains local `useState` for the input value. -- On submit, trims the input; if non-empty, calls `onAdd(text)` and clears - the field. Empty or whitespace-only input is rejected (no-op). +- Props: `{ onAdd: (text: string) => void }` +- Maintains local state for the input field value. +- On form submit, trims the input; if non-empty, calls `onAdd(text)` and clears the field. +- Prevents adding empty or whitespace-only todos. ### TodoList (`src/components/TodoList.tsx`) -- Receives `todos: Todo[]`, `onToggle`, and `onDelete` as props. -- Maps over `todos` rendering a `TodoItem` for each, keyed by `todo.id`. -- When `todos.length === 0`, renders a friendly empty-state message. +- Props: `{ todos: Todo[]; onToggle: (id: string) => void; onDelete: (id: string) => void }` +- When `todos.length === 0`, renders a "No todos yet" message. +- Otherwise maps over `todos` and renders a `` for each, passing through `onToggle` and `onDelete`. ### TodoItem (`src/components/TodoItem.tsx`) -- Receives a single `todo: Todo`, `onToggle(id: string)`, and - `onDelete(id: string)` as props. -- Renders a checkbox bound to `todo.completed` that calls `onToggle(todo.id)` - on change. -- Renders `todo.text` with `text-decoration: line-through` when `completed` - is `true`. -- Renders a "Delete" button that calls `onDelete(todo.id)` on click. -- Pure presentational — contains no internal state. +- Props: `{ todo: Todo; onToggle: (id: string) => void; onDelete: (id: string) => void }` +- Renders a checkbox bound to `todo.completed`, the todo text, and a delete button. +- Checkbox change calls `onToggle(todo.id)`. +- Delete button click calls `onDelete(todo.id)`. ## Data Flow -The app follows the standard React unidirectional data-flow pattern: +The application follows the standard React **unidirectional data flow** (props-down, callbacks-up): -1. **Props down** — `App` passes the `todos` array and callback functions down - through `TodoList` and `TodoInput`. -2. **Callbacks up** — Child components invoke callbacks (`onAdd`, `onToggle`, - `onDelete`) to request state changes. -3. **State update** — `App` updates its `useState` hook, triggering a re-render - that flows new props back down the tree. - -No events are emitted sideways between siblings; all communication goes through -the parent. +1. **State** lives exclusively in `App` via `useState`. +2. **Props flow down**: `App` passes `todos` to `TodoList`, which passes individual `todo` objects to each `TodoItem`. +3. **Callbacks flow up**: `App` passes `addTodo`, `toggleTodo`, and `deleteTodo` as callback props. Child components invoke these callbacks in response to user events (form submit, checkbox change, button click). +4. When a callback updates state, React re-renders the affected subtree. ## State Management -The application uses **only React `useState`** for state management. No external -state libraries (Redux, Zustand, MobX, etc.) are used. The single -`useState` in `App` is the sole source of truth. +Only React's built-in `useState` hook is used for state management. No external state libraries (Redux, Zustand, Jotai, MobX, etc.) are used. The todo array is the single source of truth, owned by `App`. ## File Structure @@ -98,18 +83,24 @@ src/ ├── types/ │ └── Todo.ts # Todo interface definition └── components/ - ├── TodoInput.tsx # New-todo input form - ├── TodoList.tsx # List container - └── TodoItem.tsx # Single todo row + ├── TodoInput.tsx # Text input + add button + ├── TodoList.tsx # List container, empty-state handling + └── TodoItem.tsx # Single todo row with toggle & delete ``` +## ID Generation + +Todo IDs are generated using `crypto.randomUUID()`, which produces RFC 4122 v4 UUIDs. This avoids collisions without needing an auto-increment counter or external library. + +## Persistence + +No persistence mechanism (localStorage, IndexedDB, backend API) is included in the base plan. All data is lost on page refresh. + ## Edge Cases -- **Empty list** — `TodoList` gracefully renders an empty-state message when - `todos` has length 0. -- **Blank input** — `TodoInput` prevents adding empty or whitespace-only todos. -- **ID collisions** — IDs are generated with `crypto.randomUUID()`, which - provides sufficient uniqueness for a client-side app. -- **No persistence** — Todos live only in React state; refreshing the page - clears all data. No `localStorage` or backend persistence is included in - the base plan. +| Scenario | Handling | +|---|---| +| Empty todo list | `TodoList` renders "No todos yet" message | +| Whitespace-only input | `TodoInput` trims and rejects empty strings | +| Rapid toggles | State updates are functional (`setTodos(prev => ...)`) to avoid stale closures | +| Duplicate text | Allowed — each todo has a unique UUID regardless of text content | diff --git a/RUNNING.md b/RUNNING.md index 6110080..5ab4746 100644 --- a/RUNNING.md +++ b/RUNNING.md @@ -1,4 +1,4 @@ -# Running the Application +# Running the Mini React Todo App ## TEAM_BRIEF stack: TypeScript/React+Vite @@ -10,36 +10,34 @@ coverage_applies: false ## Prerequisites -- Node.js >= 18 -- npm >= 9 (or Docker + Docker Compose) +- **Docker** and **Docker Compose** installed, OR +- **Node.js >= 18** with npm -## Local Development (npm) +## Running with Docker ```bash -npm install -npm run dev +docker compose up --build ``` -Open in your browser. +Open [http://localhost:5173](http://localhost:5173) in your browser. -## Docker +## Running Locally (without Docker) ```bash -docker compose up --build +npm install +npm run dev ``` -Open in your browser. +Open [http://localhost:5173](http://localhost:5173) in your browser. ## Authentication -No authentication is required. The app runs entirely in the browser with no -backend dependencies. +No authentication is required. The app runs entirely in the browser with no backend. -## Running Tests +## Build for Production ```bash -pytest tests/ +npm run build ``` -Tests validate component structure and file presence. No browser or Node.js -runtime is required for the test suite. +Output is written to `dist/`. diff --git a/src/components/TodoItem.tsx b/src/components/TodoItem.tsx index 630ea0c..d2d76e2 100644 --- a/src/components/TodoItem.tsx +++ b/src/components/TodoItem.tsx @@ -5,45 +5,35 @@ import { Todo } from "../types/Todo"; * Props for the TodoItem component. */ export interface TodoItemProps { - /** The todo object to render. */ + /** The todo item to render. */ todo: Todo; - /** Callback invoked with the todo's id when the checkbox is toggled. */ + /** Callback invoked with the todo id when the user toggles completion. */ onToggle: (id: string) => void; - /** Callback invoked with the todo's id when the delete button is clicked. */ + /** Callback invoked with the todo id when the user deletes the item. */ onDelete: (id: string) => void; } /** - * Presentational component that renders a single todo item. - * - * Displays the todo text with a checkbox for toggling completion status - * and a delete button for removing the item. When the todo is completed, - * the text is rendered with a line-through style. + * Renders a single todo item with toggle and delete controls. */ const TodoItem: React.FC = ({ todo, onToggle, onDelete }) => { return ( -
  • - onToggle(todo.id)} - aria-label={`Toggle ${todo.text}`} - /> - + + onToggle(todo.id)} + aria-label={`Toggle ${todo.text}`} + /> + {todo.text} + +
  • ); -}; +} export default TodoInput; diff --git a/src/components/TodoItem.tsx b/src/components/TodoItem.tsx index d2d76e2..9cf5f38 100644 --- a/src/components/TodoItem.tsx +++ b/src/components/TodoItem.tsx @@ -1,47 +1,44 @@ -import React from "react"; -import { Todo } from "../types/Todo"; +import React from 'react'; +import { Todo } from '../types/Todo'; /** * Props for the TodoItem component. */ -export interface TodoItemProps { +interface TodoItemProps { /** The todo item to render. */ todo: Todo; - /** Callback invoked with the todo id when the user toggles completion. */ + /** Callback to toggle the completed status of this todo. */ onToggle: (id: string) => void; - /** Callback invoked with the todo id when the user deletes the item. */ + /** Callback to delete this todo. */ onDelete: (id: string) => void; } /** - * Renders a single todo item with toggle and delete controls. + * Renders a single todo item with a checkbox, text, and delete button. + * + * Completed items are displayed with a line-through style. */ -const TodoItem: React.FC = ({ todo, onToggle, onDelete }) => { +function TodoItem({ todo, onToggle, onDelete }: TodoItemProps): React.JSX.Element { return ( -
  • -
  • +
  • ); -}; +} export default TodoItem; diff --git a/src/components/TodoList.tsx b/src/components/TodoList.tsx index deda8e1..88ee8bc 100644 --- a/src/components/TodoList.tsx +++ b/src/components/TodoList.tsx @@ -1,32 +1,31 @@ -import React from "react"; -import { Todo } from "../types/Todo"; -import TodoItem from "./TodoItem"; +import React from 'react'; +import { Todo } from '../types/Todo'; +import TodoItem from './TodoItem'; /** * Props for the TodoList component. */ -export interface TodoListProps { +interface TodoListProps { /** Array of todo items to display. */ todos: Todo[]; - /** Callback invoked with a todo's id when the user toggles its completion status. */ + /** Callback to toggle a todo's completed status by id. */ onToggle: (id: string) => void; - /** Callback invoked with a todo's id when the user deletes it. */ + /** Callback to delete a todo by id. */ onDelete: (id: string) => void; } /** - * Renders a list of todo items. + * Renders the list of todo items. * - * Maps over the provided `todos` array and renders a `TodoItem` for each. - * When the list is empty, displays a "No todos yet" message. + * Displays a message when the list is empty. */ -const TodoList: React.FC = ({ todos, onToggle, onDelete }) => { +function TodoList({ todos, onToggle, onDelete }: TodoListProps): React.JSX.Element { if (todos.length === 0) { - return

    No todos yet

    ; + return

    No todos yet. Add one above!

    ; } return ( -
      +
        {todos.map((todo) => ( = ({ todos, onToggle, onDelete }) => { ))}
      ); -}; +} export default TodoList; diff --git a/src/types/Todo.ts b/src/types/Todo.ts index 8d7a784..32f3637 100644 --- a/src/types/Todo.ts +++ b/src/types/Todo.ts @@ -1,11 +1,11 @@ /** - * Core Todo item type used throughout the application. + * Represents a single todo item in the application. */ export interface Todo { - /** Unique identifier generated via crypto.randomUUID(). */ + /** Unique identifier generated via crypto.randomUUID() */ id: string; - /** The text content of the todo item. */ + /** The text content of the todo */ text: string; - /** Whether the todo has been completed. */ + /** Whether the todo has been completed */ completed: boolean; } From 38c00093fa40e79f19192479c2e7bfabdf3310e2 Mon Sep 17 00:00:00 2001 From: FORGE Date: Fri, 10 Apr 2026 00:20:07 +0000 Subject: [PATCH 6/7] feat: Create RUNNING.md with instructions Run: c6b60c93-bf32-41d4-a111-87879d73c789 Task: 9f6af634-08ea-4b93-8e31-48d251c393f8 Agent: builder --- ARCHITECTURE.md | 177 +++++++++++++++++++++++++++++++----------------- RUNNING.md | 74 +++++++++++++++++++- 2 files changed, 186 insertions(+), 65 deletions(-) diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index 4257abe..950e95d 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -1,100 +1,153 @@ -# Mini React Todo App — Architecture +# Architecture — Mini React Todo App + +This document describes the complete architecture of the Mini React Todo App, +a client-side single-page application built with React, TypeScript, and Vite. ## Overview -A client-side Todo application built with React, TypeScript, and Vite. All state -is managed in-memory on the client — there is no backend or persistence layer. +The application lets users create, complete, and delete todo items. All state +lives in the browser — there is no backend persistence. The UI is composed of +a small tree of React functional components that communicate via props and +callback functions following a strict unidirectional data-flow pattern. ## Type Definitions -```typescript -// src/types/Todo.ts +The core domain type is defined in `src/types/Todo.ts`: + +```ts export interface Todo { - id: string; // Generated via crypto.randomUUID() - text: string; // The todo content - completed: boolean; // Whether the todo is done + id: string; // Unique identifier (crypto.randomUUID()) + text: string; // The todo's display text + completed: boolean; // Whether the todo has been marked as done } ``` +- **id** (`string`): Generated via `crypto.randomUUID()` to avoid collisions. +- **text** (`string`): The user-provided description of the todo. +- **completed** (`boolean`): Toggled by the user; defaults to `false`. + ## Component Tree ``` App ├── TodoInput └── TodoList - └── TodoItem (one per todo) + ├── TodoItem + ├── TodoItem + └── … ``` -## Component Responsibilities - -### App (`src/App.tsx`) - -- Owns the single source of truth: `useState`. -- Defines three state-mutation functions: - - **addTodo(text: string)** — creates a `Todo` with `crypto.randomUUID()`, - prepends it to the list. - - **toggleTodo(id: string)** — flips the `completed` boolean of the matching todo. - - **deleteTodo(id: string)** — removes the todo with the given id. -- Renders `` and ``, passing data and callbacks as props. -- Imports `App.css` for application-wide styling. - -### TodoInput (`src/components/TodoInput.tsx`) - -- Manages its own local `useState` for the text field. -- On form submit, trims the input; ignores empty/whitespace-only strings. -- Calls `props.onAdd(trimmedText)` and clears the field. +- `App` is the root component that owns all application state. +- `TodoInput` is a controlled form for adding new todos. +- `TodoList` renders the array of todos (or handles the empty-list case). +- `TodoItem` renders a single todo with toggle and delete controls. -### TodoList (`src/components/TodoList.tsx`) - -- Receives `todos`, `onToggle`, and `onDelete` as props. -- When `todos.length === 0`, renders an empty-state message. -- Otherwise maps over `todos` and renders a `` for each. - -### TodoItem (`src/components/TodoItem.tsx`) +## Component Responsibilities -- Receives a single `todo`, `onToggle`, and `onDelete` as props. -- Renders a checkbox (bound to `todo.completed`), the text, and a Delete button. -- Applies a `completed` CSS class when the item is done (line-through style). +### App + +- **File:** `src/App.tsx` +- **State:** Owns the single source of truth via `useState([])`. +- **Functions defined here:** + - `addTodo(text: string): void` — Creates a new `Todo` object (using `crypto.randomUUID()` for the id, the supplied text, and `completed: false`) and prepends it to the list. + - `toggleTodo(id: string): void` — Flips the `completed` flag of the todo with the matching id. + - `deleteTodo(id: string): void` — Removes the todo with the matching id from the list. +- Renders `` and ``, passing state and callbacks as props. + +### TodoInput + +- **File:** `src/components/TodoInput.tsx` +- **Props:** + ```ts + interface TodoInputProps { + onAdd: (text: string) => void; + } + ``` +- Manages its own local `useState("")` for the input field. +- On form submission: + 1. Trims the input value. + 2. If the trimmed value is empty, does **nothing** (prevents adding empty/whitespace-only todos). + 3. Otherwise calls `onAdd(trimmedText)` and clears the input. + +### TodoList + +- **File:** `src/components/TodoList.tsx` +- **Props:** + ```ts + interface TodoListProps { + todos: Todo[]; + onToggle: (id: string) => void; + onDelete: (id: string) => void; + } + ``` +- If `todos.length === 0`, renders a friendly empty-state message (e.g. "No todos yet"). +- Otherwise maps over `todos` and renders a `` for each, passing the individual todo and the callbacks. + +### TodoItem + +- **File:** `src/components/TodoItem.tsx` +- **Props:** + ```ts + interface TodoItemProps { + todo: Todo; + onToggle: (id: string) => void; + onDelete: (id: string) => void; + } + ``` +- Renders the todo text, a checkbox/button to toggle completion, and a delete button. +- Applies a visual style (e.g. strikethrough) when `todo.completed` is `true`. ## Data Flow -The app follows the standard React unidirectional data-flow pattern: +The application follows the standard React unidirectional data-flow pattern: -1. **Props down** — `App` passes the `todos` array and callback functions - (`onAdd`, `onToggle`, `onDelete`) to child components. -2. **Callbacks up** — child components invoke the callbacks to request state - changes; `App` updates its `useState` accordingly, triggering a re-render. +1. **Props down:** `App` passes the `todos` array and callback functions (`onAdd`, `onToggle`, `onDelete`) as props to child components. +2. **Callbacks up:** Child components invoke the callbacks when the user interacts with the UI (e.g. submitting the input form, clicking a checkbox, clicking a delete button). +3. **State update:** The callback in `App` calls the `useState` setter, which triggers a re-render of the component tree with the updated state. -No events are emitted sideways between sibling components. +There is no prop drilling beyond two levels, and no need for React Context or any external state library. ## State Management -Only React's built-in `useState` hook is used. No external state management -libraries (Redux, Zustand, Jotai, MobX, etc.) are part of this project. +State management uses **only React `useState`** — no external state management libraries (Redux, Zustand, MobX, Jotai, etc.) are used. + +- `App` holds `const [todos, setTodos] = useState([]);` +- `TodoInput` holds `const [text, setText] = useState("");` -- `App` holds `useState([])` — the single authoritative list. -- `TodoInput` holds `useState('')` — the controlled input value. +These two pieces of state are the only state in the entire application. ## File Structure ``` src/ +├── main.tsx # ReactDOM.createRoot entry point +├── App.tsx # Root component — state owner +├── App.css # Styles for the App component +├── index.css # Global/base styles ├── types/ -│ └── Todo.ts # Todo interface definition -├── components/ -│ ├── TodoInput.tsx # New-todo input form -│ ├── TodoList.tsx # Renders the list of TodoItems -│ └── TodoItem.tsx # Single todo row -├── App.tsx # Root component with state management -├── App.css # Application styles -└── main.tsx # Vite entry point (renders ) +│ └── Todo.ts # Todo interface definition +└── components/ + ├── TodoInput.tsx # Controlled input form component + ├── TodoList.tsx # List renderer (handles empty state) + └── TodoItem.tsx # Single todo row with toggle & delete +``` + +Supporting project-root files: + +``` +index.html # Vite HTML entry point +package.json # Dependencies and npm scripts +tsconfig.json # TypeScript compiler configuration +vite.config.ts # Vite build configuration ``` -## Edge Cases +## Edge Cases & Design Decisions -- **Empty list**: `TodoList` renders a friendly empty-state message. -- **Whitespace-only input**: `TodoInput` trims and rejects empty strings. -- **ID collisions**: `crypto.randomUUID()` produces v4 UUIDs with negligible - collision probability. -- **No persistence**: Refreshing the page clears all todos. No `localStorage` - or server sync is included in this version. +| Concern | Decision | +|---|---| +| Empty/whitespace todo text | `TodoInput` trims input and silently rejects empty strings | +| Empty todo list | `TodoList` renders a placeholder message when `todos.length === 0` | +| Unique IDs | `crypto.randomUUID()` is used — supported in all modern browsers | +| Persistence | **None** — no `localStorage`, no backend. Todos are lost on page refresh | +| Styling | Plain CSS; no CSS-in-JS or utility framework required | +| Testing | Component behaviour is tested via pytest against the built output | diff --git a/RUNNING.md b/RUNNING.md index a7d851a..d1a65c3 100644 --- a/RUNNING.md +++ b/RUNNING.md @@ -10,16 +10,52 @@ coverage_applies: false ## Prerequisites -- Node.js 18+ and npm (or Docker) +- Node.js 18+ and npm +- Python 3.9+ and pip (for running tests) -## Local Development (npm) +## Installing Dependencies ```bash npm install +``` + +This installs all Node.js dependencies listed in `package.json`, including React, TypeScript, and Vite. + +## Running the Dev Server + +```bash npm run dev ``` -Open **http://localhost:5173** in your browser. +Open **http://localhost:5173** in your browser. +The Vite dev server supports hot module replacement (HMR) — changes to source files are reflected instantly. + +## Building for Production + +```bash +npm run build +``` + +This creates an optimised production bundle in the `dist/` directory. +To preview the production build locally: + +```bash +npm run preview +``` + +## Running Tests + +```bash +pip install pytest +pytest +``` + +Or, if you prefer to be explicit about the test directory: + +```bash +pip install pytest +pytest tests/ +``` ## Docker @@ -38,6 +74,38 @@ No authentication is required. The app is fully client-side with no backend. | Command | Description | |-------------------|------------------------------------| +| `npm install` | Install all dependencies | | `npm run dev` | Start the Vite dev server | | `npm run build` | Create a production build | | `npm run preview` | Preview the production build | + +## Expected File Structure + +``` +. +├── RUNNING.md +├── ARCHITECTURE.md +├── package.json +├── tsconfig.json +├── vite.config.ts +├── index.html +├── public/ +├── src/ +│ ├── main.tsx +│ ├── App.tsx +│ ├── App.css +│ ├── index.css +│ ├── types/ +│ │ └── Todo.ts +│ └── components/ +│ ├── TodoInput.tsx +│ ├── TodoList.tsx +│ └── TodoItem.tsx +├── tests/ +│ └── test_*.py +├── conftest.py +├── main.py +├── models.py +├── routes.py +└── storage.py +``` From 96df37ca38901738b732c26fd85b913cd5e669d8 Mon Sep 17 00:00:00 2001 From: FORGE Date: Fri, 10 Apr 2026 00:21:43 +0000 Subject: [PATCH 7/7] feat: Write complete test suite for file structure validation Run: c6b60c93-bf32-41d4-a111-87879d73c789 Task: d6a86afd-ba04-4050-a01f-57ad89bde9c4 Agent: builder --- ARCHITECTURE.md | 185 ++++++++++++++--------------------- RUNNING.md | 104 +++++--------------- index.html | 4 +- package.json | 22 +++++ src/App.css | 115 +++++++++------------- src/App.tsx | 30 ++---- src/components/TodoInput.tsx | 21 ++-- src/components/TodoItem.tsx | 45 ++++----- src/components/TodoList.tsx | 27 +++-- src/main.tsx | 14 +-- src/types.ts | 10 +- tests/test_file_structure.py | 111 +++++++++++++++++++++ tsconfig.json | 20 ++++ vite.config.ts | 10 ++ 14 files changed, 369 insertions(+), 349 deletions(-) create mode 100644 package.json create mode 100644 tests/test_file_structure.py create mode 100644 tsconfig.json create mode 100644 vite.config.ts diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index 950e95d..59828b0 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -1,30 +1,29 @@ # Architecture — Mini React Todo App This document describes the complete architecture of the Mini React Todo App, -a client-side single-page application built with React, TypeScript, and Vite. +a single-page application built with React, TypeScript, and Vite. ## Overview -The application lets users create, complete, and delete todo items. All state -lives in the browser — there is no backend persistence. The UI is composed of -a small tree of React functional components that communicate via props and -callback functions following a strict unidirectional data-flow pattern. +A lightweight, client-side todo application that allows users to add, toggle, +and delete todo items. All state lives in the browser — there is no backend +or persistence layer. ## Type Definitions -The core domain type is defined in `src/types/Todo.ts`: +The core data model is defined in `src/types.ts`: -```ts +```typescript export interface Todo { - id: string; // Unique identifier (crypto.randomUUID()) - text: string; // The todo's display text - completed: boolean; // Whether the todo has been marked as done + id: string; // Unique identifier (crypto.randomUUID()) + text: string; // The todo item text + completed: boolean; // Whether the item has been completed } ``` - **id** (`string`): Generated via `crypto.randomUUID()` to avoid collisions. -- **text** (`string`): The user-provided description of the todo. -- **completed** (`boolean`): Toggled by the user; defaults to `false`. +- **text** (`string`): User-supplied description of the task. +- **completed** (`boolean`): Toggled between `true` and `false`. ## Component Tree @@ -32,122 +31,90 @@ export interface Todo { App ├── TodoInput └── TodoList - ├── TodoItem - ├── TodoItem - └── … + └── TodoItem (one per todo) ``` -- `App` is the root component that owns all application state. -- `TodoInput` is a controlled form for adding new todos. -- `TodoList` renders the array of todos (or handles the empty-list case). -- `TodoItem` renders a single todo with toggle and delete controls. +- **App** → **TodoInput**: passes `addTodo` callback. +- **App** → **TodoList**: passes `todos`, `toggleTodo`, and `deleteTodo`. +- **TodoList** → **TodoItem**: passes individual `todo`, `toggleTodo`, and `deleteTodo`. ## Component Responsibilities -### App - -- **File:** `src/App.tsx` -- **State:** Owns the single source of truth via `useState([])`. -- **Functions defined here:** - - `addTodo(text: string): void` — Creates a new `Todo` object (using `crypto.randomUUID()` for the id, the supplied text, and `completed: false`) and prepends it to the list. - - `toggleTodo(id: string): void` — Flips the `completed` flag of the todo with the matching id. - - `deleteTodo(id: string): void` — Removes the todo with the matching id from the list. -- Renders `` and ``, passing state and callbacks as props. - -### TodoInput - -- **File:** `src/components/TodoInput.tsx` -- **Props:** - ```ts - interface TodoInputProps { - onAdd: (text: string) => void; - } - ``` -- Manages its own local `useState("")` for the input field. +### App (`src/App.tsx`) + +- Owns application state via `useState`. +- Defines three state-mutation functions: + - `addTodo(text: string): void` — creates a new `Todo` with `crypto.randomUUID()` and prepends it. + - `toggleTodo(id: string): void` — flips the `completed` flag of the matching todo. + - `deleteTodo(id: string): void` — removes the matching todo from the list. +- Renders `` and `` with appropriate props. + +### TodoInput (`src/components/TodoInput.tsx`) + +- Maintains local `useState` for the input field value. - On form submission: - 1. Trims the input value. - 2. If the trimmed value is empty, does **nothing** (prevents adding empty/whitespace-only todos). - 3. Otherwise calls `onAdd(trimmedText)` and clears the input. - -### TodoList - -- **File:** `src/components/TodoList.tsx` -- **Props:** - ```ts - interface TodoListProps { - todos: Todo[]; - onToggle: (id: string) => void; - onDelete: (id: string) => void; - } - ``` -- If `todos.length === 0`, renders a friendly empty-state message (e.g. "No todos yet"). -- Otherwise maps over `todos` and renders a `` for each, passing the individual todo and the callbacks. - -### TodoItem - -- **File:** `src/components/TodoItem.tsx` -- **Props:** - ```ts - interface TodoItemProps { - todo: Todo; - onToggle: (id: string) => void; - onDelete: (id: string) => void; - } - ``` -- Renders the todo text, a checkbox/button to toggle completion, and a delete button. -- Applies a visual style (e.g. strikethrough) when `todo.completed` is `true`. + - Trims whitespace; if the result is empty, does **not** call `addTodo`. + - Otherwise calls `addTodo(trimmedText)` and clears the input. +- Props: `{ addTodo: (text: string) => void }`. -## Data Flow +### TodoList (`src/components/TodoList.tsx`) -The application follows the standard React unidirectional data-flow pattern: +- Receives the full `todos` array, `toggleTodo`, and `deleteTodo` as props. +- Maps over `todos` and renders a `` for each entry. +- Handles the empty-list case gracefully (renders a helpful message when `todos.length === 0`). +- Props: `{ todos: Todo[]; toggleTodo: (id: string) => void; deleteTodo: (id: string) => void }`. -1. **Props down:** `App` passes the `todos` array and callback functions (`onAdd`, `onToggle`, `onDelete`) as props to child components. -2. **Callbacks up:** Child components invoke the callbacks when the user interacts with the UI (e.g. submitting the input form, clicking a checkbox, clicking a delete button). -3. **State update:** The callback in `App` calls the `useState` setter, which triggers a re-render of the component tree with the updated state. +### TodoItem (`src/components/TodoItem.tsx`) -There is no prop drilling beyond two levels, and no need for React Context or any external state library. +- Renders a single todo with: + - A checkbox bound to `todo.completed` that calls `toggleTodo(todo.id)` on change. + - The todo text, visually struck through when completed. + - A delete button that calls `deleteTodo(todo.id)`. +- Props: `{ todo: Todo; toggleTodo: (id: string) => void; deleteTodo: (id: string) => void }`. -## State Management +## Data Flow -State management uses **only React `useState`** — no external state management libraries (Redux, Zustand, MobX, Jotai, etc.) are used. +The application follows a strict **unidirectional data flow**: -- `App` holds `const [todos, setTodos] = useState([]);` -- `TodoInput` holds `const [text, setText] = useState("");` +1. **State** lives exclusively in `App` via `useState`. +2. **Props down**: `App` passes `todos` and callback functions down to children. +3. **Callbacks up**: Child components invoke callbacks (`addTodo`, `toggleTodo`, + `deleteTodo`) which update state in `App`, triggering a re-render. -These two pieces of state are the only state in the entire application. +No child component mutates state directly. -## File Structure +## State Management -``` -src/ -├── main.tsx # ReactDOM.createRoot entry point -├── App.tsx # Root component — state owner -├── App.css # Styles for the App component -├── index.css # Global/base styles -├── types/ -│ └── Todo.ts # Todo interface definition -└── components/ - ├── TodoInput.tsx # Controlled input form component - ├── TodoList.tsx # List renderer (handles empty state) - └── TodoItem.tsx # Single todo row with toggle & delete -``` +Only **React `useState`** is used for state management. No external state +libraries (Redux, Zustand, Jotai, MobX, etc.) are used. The entire +application state is a single `Todo[]` array held in the `App` component. -Supporting project-root files: +## File Structure ``` -index.html # Vite HTML entry point -package.json # Dependencies and npm scripts -tsconfig.json # TypeScript compiler configuration -vite.config.ts # Vite build configuration +/ +├── index.html # Vite HTML entry point +├── package.json # Dependencies and scripts +├── tsconfig.json # TypeScript configuration +├── vite.config.ts # Vite build configuration +├── RUNNING.md # Setup / run instructions +├── ARCHITECTURE.md # This file +└── src/ + ├── main.tsx # React DOM entry point + ├── App.tsx # Root component with state + ├── App.css # Application styles + ├── types.ts # Todo interface definition + ├── vite-env.d.ts # Vite client type references + └── components/ + ├── TodoInput.tsx # Input form component + ├── TodoItem.tsx # Single todo row component + └── TodoList.tsx # List container component ``` -## Edge Cases & Design Decisions +## Edge Cases -| Concern | Decision | -|---|---| -| Empty/whitespace todo text | `TodoInput` trims input and silently rejects empty strings | -| Empty todo list | `TodoList` renders a placeholder message when `todos.length === 0` | -| Unique IDs | `crypto.randomUUID()` is used — supported in all modern browsers | -| Persistence | **None** — no `localStorage`, no backend. Todos are lost on page refresh | -| Styling | Plain CSS; no CSS-in-JS or utility framework required | -| Testing | Component behaviour is tested via pytest against the built output | +- **Empty input**: `TodoInput` trims whitespace and prevents adding empty todos. +- **Empty list**: `TodoList` renders a fallback message when there are no todos. +- **ID collisions**: `crypto.randomUUID()` provides sufficient uniqueness. +- **No persistence**: State resets on page reload — localStorage is not included + in this base implementation. diff --git a/RUNNING.md b/RUNNING.md index d1a65c3..3c05eef 100644 --- a/RUNNING.md +++ b/RUNNING.md @@ -1,111 +1,51 @@ # Running the Mini React Todo App -## TEAM_BRIEF -stack: TypeScript/React+Vite -test_runner: pytest tests/ -lint_tool: none -coverage_tool: none -coverage_threshold: 0 -coverage_applies: false - ## Prerequisites -- Node.js 18+ and npm -- Python 3.9+ and pip (for running tests) +- Node.js >= 18 +- npm >= 9 +- Python >= 3.9 (for running the test suite) -## Installing Dependencies +## Quick Start ```bash +# Install dependencies npm install -``` -This installs all Node.js dependencies listed in `package.json`, including React, TypeScript, and Vite. - -## Running the Dev Server - -```bash +# Start the Vite dev server npm run dev ``` -Open **http://localhost:5173** in your browser. -The Vite dev server supports hot module replacement (HMR) — changes to source files are reflected instantly. - -## Building for Production - -```bash -npm run build -``` - -This creates an optimised production bundle in the `dist/` directory. -To preview the production build locally: - -```bash -npm run preview -``` +The application will be available at **http://localhost:5173**. ## Running Tests ```bash +# Install Python test dependencies pip install pytest -pytest -``` -Or, if you prefer to be explicit about the test directory: - -```bash -pip install pytest +# Run the test suite pytest tests/ ``` -## Docker +## Docker (optional) ```bash -docker build -t todo-react-app . -docker run -p 5173:5173 todo-react-app +docker compose up --build ``` Open **http://localhost:5173** in your browser. -## Authentication - -No authentication is required. The app is fully client-side with no backend. - -## Available Scripts - -| Command | Description | -|-------------------|------------------------------------| -| `npm install` | Install all dependencies | -| `npm run dev` | Start the Vite dev server | -| `npm run build` | Create a production build | -| `npm run preview` | Preview the production build | +## Notes -## Expected File Structure +- No authentication is required. +- No demo credentials needed. +- All state is in-memory (browser only); refreshing the page clears todos. -``` -. -├── RUNNING.md -├── ARCHITECTURE.md -├── package.json -├── tsconfig.json -├── vite.config.ts -├── index.html -├── public/ -├── src/ -│ ├── main.tsx -│ ├── App.tsx -│ ├── App.css -│ ├── index.css -│ ├── types/ -│ │ └── Todo.ts -│ └── components/ -│ ├── TodoInput.tsx -│ ├── TodoList.tsx -│ └── TodoItem.tsx -├── tests/ -│ └── test_*.py -├── conftest.py -├── main.py -├── models.py -├── routes.py -└── storage.py -``` +## TEAM_BRIEF +stack: TypeScript/React+Vite +test_runner: pytest tests/ +lint_tool: none +coverage_tool: none +coverage_threshold: 0 +coverage_applies: false diff --git a/index.html b/index.html index c92cebd..f661913 100644 --- a/index.html +++ b/index.html @@ -1,9 +1,9 @@ - + - Todo App + Mini React Todo App
      diff --git a/package.json b/package.json new file mode 100644 index 0000000..d8007b8 --- /dev/null +++ b/package.json @@ -0,0 +1,22 @@ +{ + "name": "mini-react-todo-app", + "private": true, + "version": "1.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "tsc && vite build", + "preview": "vite preview" + }, + "dependencies": { + "react": "^18.2.0", + "react-dom": "^18.2.0" + }, + "devDependencies": { + "@types/react": "^18.2.0", + "@types/react-dom": "^18.2.0", + "typescript": "^5.3.0", + "vite": "^5.0.0", + "@vitejs/plugin-react": "^4.2.0" + } +} diff --git a/src/App.css b/src/App.css index 8280bb5..916bb96 100644 --- a/src/App.css +++ b/src/App.css @@ -1,79 +1,72 @@ -/* ===== App Layout ===== */ +/* Application styles for the Mini React Todo App */ -.app-container { - max-width: 520px; - margin: 2rem auto; - padding: 1.5rem; +* { + box-sizing: border-box; + margin: 0; + padding: 0; +} + +body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, - Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif; + Oxygen, Ubuntu, Cantarell, sans-serif; + background-color: #f5f5f5; color: #333; } -.app-container h1 { +.app { + max-width: 600px; + margin: 2rem auto; + padding: 1.5rem; + background: #fff; + border-radius: 8px; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); +} + +.app h1 { text-align: center; margin-bottom: 1.5rem; - font-size: 1.8rem; - color: #222; + color: #1a1a2e; } -/* ===== TodoInput ===== */ - .todo-input-form { display: flex; gap: 0.5rem; margin-bottom: 1.5rem; } -.todo-input { +.todo-input-form input { flex: 1; - padding: 0.6rem 0.8rem; - font-size: 1rem; + padding: 0.5rem 0.75rem; border: 1px solid #ccc; border-radius: 4px; - outline: none; - transition: border-color 0.2s; -} - -.todo-input:focus { - border-color: #4a90d9; + font-size: 1rem; } -.todo-add-btn { - padding: 0.6rem 1.2rem; - font-size: 1rem; - background-color: #4a90d9; +.todo-input-form button { + padding: 0.5rem 1rem; + background-color: #1a1a2e; color: #fff; border: none; border-radius: 4px; cursor: pointer; - transition: background-color 0.2s; -} - -.todo-add-btn:hover { - background-color: #357abd; + font-size: 1rem; } -/* ===== TodoList ===== */ - -.todo-list { - list-style: none; - padding: 0; - margin: 0; +.todo-input-form button:hover { + background-color: #16213e; } -.todo-empty { +.todo-list-empty { text-align: center; - color: #888; - font-style: italic; + color: #999; + padding: 1rem 0; } -/* ===== TodoItem ===== */ - .todo-item { display: flex; align-items: center; - justify-content: space-between; - padding: 0.6rem 0.4rem; + gap: 0.75rem; + padding: 0.75rem 0; border-bottom: 1px solid #eee; } @@ -81,43 +74,31 @@ border-bottom: none; } -.todo-label { - display: flex; - align-items: center; - gap: 0.5rem; - flex: 1; - cursor: pointer; -} - -.todo-checkbox { - width: 18px; - height: 18px; +.todo-item input[type='checkbox'] { + width: 1.2rem; + height: 1.2rem; cursor: pointer; - accent-color: #4a90d9; } -.todo-text { +.todo-item .todo-text { + flex: 1; font-size: 1rem; } -/* Completed item styling */ -.todo-item.completed .todo-text { +.todo-item .todo-text.completed { text-decoration: line-through; color: #999; } -.todo-delete-btn { - padding: 0.3rem 0.7rem; - font-size: 0.85rem; - background-color: #e74c3c; - color: #fff; +.todo-item .delete-btn { + background: none; border: none; - border-radius: 4px; + color: #e74c3c; cursor: pointer; - transition: background-color 0.2s; - margin-left: 0.5rem; + font-size: 1.1rem; + padding: 0.25rem 0.5rem; } -.todo-delete-btn:hover { - background-color: #c0392b; +.todo-item .delete-btn:hover { + color: #c0392b; } diff --git a/src/App.tsx b/src/App.tsx index a6c0224..0fdbaa1 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,5 +1,5 @@ import React, { useState } from 'react'; -import { Todo } from './types/Todo'; +import { Todo } from './types'; import TodoInput from './components/TodoInput'; import TodoList from './components/TodoList'; import './App.css'; @@ -7,16 +7,12 @@ import './App.css'; /** * Root application component. * - * Owns the todo list state and provides addTodo, toggleTodo, and deleteTodo - * callbacks that are passed down to child components via props. + * Owns the todo list state and provides addTodo, toggleTodo, and + * deleteTodo callbacks to child components. */ -function App(): React.JSX.Element { +const App: React.FC = () => { const [todos, setTodos] = useState([]); - /** - * Add a new todo with the given text. - * Generates a unique id using crypto.randomUUID(). - */ const addTodo = (text: string): void => { const newTodo: Todo = { id: crypto.randomUUID(), @@ -26,31 +22,25 @@ function App(): React.JSX.Element { setTodos((prev) => [newTodo, ...prev]); }; - /** - * Toggle the completed boolean of the todo with the given id. - */ const toggleTodo = (id: string): void => { setTodos((prev) => prev.map((todo) => - todo.id === id ? { ...todo, completed: !todo.completed } : todo - ) + todo.id === id ? { ...todo, completed: !todo.completed } : todo, + ), ); }; - /** - * Delete the todo with the given id. - */ const deleteTodo = (id: string): void => { setTodos((prev) => prev.filter((todo) => todo.id !== id)); }; return ( -
      +

      Todo App

      - - + +
      ); -} +}; export default App; diff --git a/src/components/TodoInput.tsx b/src/components/TodoInput.tsx index baecf90..2853bf8 100644 --- a/src/components/TodoInput.tsx +++ b/src/components/TodoInput.tsx @@ -4,25 +4,24 @@ import React, { useState } from 'react'; * Props for the TodoInput component. */ interface TodoInputProps { - /** Callback invoked with the trimmed text when the user submits a new todo. */ - onAdd: (text: string) => void; + addTodo: (text: string) => void; } /** - * Input component that allows users to type and submit new todo items. + * Input form for creating new todo items. * - * Prevents adding empty or whitespace-only todos. + * Trims whitespace from the input and prevents adding empty todos. */ -function TodoInput({ onAdd }: TodoInputProps): React.JSX.Element { +const TodoInput: React.FC = ({ addTodo }) => { const [text, setText] = useState(''); - const handleSubmit = (e: React.FormEvent): void => { + const handleSubmit = (e: React.FormEvent): void => { e.preventDefault(); const trimmed = text.trim(); if (trimmed.length === 0) { return; } - onAdd(trimmed); + addTodo(trimmed); setText(''); }; @@ -30,16 +29,14 @@ function TodoInput({ onAdd }: TodoInputProps): React.JSX.Element {
      setText(e.target.value)} placeholder="Add a new todo..." + aria-label="Todo text" /> - +
      ); -} +}; export default TodoInput; diff --git a/src/components/TodoItem.tsx b/src/components/TodoItem.tsx index 9cf5f38..1381871 100644 --- a/src/components/TodoItem.tsx +++ b/src/components/TodoItem.tsx @@ -1,44 +1,39 @@ import React from 'react'; -import { Todo } from '../types/Todo'; +import { Todo } from '../types'; /** * Props for the TodoItem component. */ interface TodoItemProps { - /** The todo item to render. */ todo: Todo; - /** Callback to toggle the completed status of this todo. */ - onToggle: (id: string) => void; - /** Callback to delete this todo. */ - onDelete: (id: string) => void; + toggleTodo: (id: string) => void; + deleteTodo: (id: string) => void; } /** * Renders a single todo item with a checkbox, text, and delete button. - * - * Completed items are displayed with a line-through style. */ -function TodoItem({ todo, onToggle, onDelete }: TodoItemProps): React.JSX.Element { +const TodoItem: React.FC = ({ todo, toggleTodo, deleteTodo }) => { return ( -
    • - +
      + toggleTodo(todo.id)} + aria-label={`Toggle ${todo.text}`} + /> + + {todo.text} + -
    • +
      ); -} +}; export default TodoItem; diff --git a/src/components/TodoList.tsx b/src/components/TodoList.tsx index 88ee8bc..fe0b4f8 100644 --- a/src/components/TodoList.tsx +++ b/src/components/TodoList.tsx @@ -1,41 +1,36 @@ import React from 'react'; -import { Todo } from '../types/Todo'; +import { Todo } from '../types'; import TodoItem from './TodoItem'; /** * Props for the TodoList component. */ interface TodoListProps { - /** Array of todo items to display. */ todos: Todo[]; - /** Callback to toggle a todo's completed status by id. */ - onToggle: (id: string) => void; - /** Callback to delete a todo by id. */ - onDelete: (id: string) => void; + toggleTodo: (id: string) => void; + deleteTodo: (id: string) => void; } /** - * Renders the list of todo items. - * - * Displays a message when the list is empty. + * Renders the list of todo items, or a message if the list is empty. */ -function TodoList({ todos, onToggle, onDelete }: TodoListProps): React.JSX.Element { +const TodoList: React.FC = ({ todos, toggleTodo, deleteTodo }) => { if (todos.length === 0) { - return

      No todos yet. Add one above!

      ; + return

      No todos yet. Add one above!

      ; } return ( -
        +
        {todos.map((todo) => ( ))} -
      + ); -} +}; export default TodoList; diff --git a/src/main.tsx b/src/main.tsx index 9ecb6ff..98dc952 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -1,19 +1,9 @@ import React from 'react'; import ReactDOM from 'react-dom/client'; import App from './App'; +import './App.css'; -/** - * Application entry point. - * - * Mounts the root component into the DOM element with id "root". - */ -const rootElement = document.getElementById('root'); - -if (!rootElement) { - throw new Error('Root element not found. Ensure index.html contains a
      .'); -} - -ReactDOM.createRoot(rootElement).render( +ReactDOM.createRoot(document.getElementById('root')!).render( , diff --git a/src/types.ts b/src/types.ts index 719a364..2d03306 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,15 +1,17 @@ /** - * Core type definitions for the Todo application. + * Core type definitions for the Mini React Todo App. */ /** - * Represents a single todo item in the application. + * Represents a single Todo item. */ export interface Todo { - /** Unique identifier for the todo item. */ + /** Unique identifier generated via crypto.randomUUID(). */ id: string; - /** The text content of the todo item. */ + + /** The text description of the todo item. */ text: string; + /** Whether the todo item has been completed. */ completed: boolean; } diff --git a/tests/test_file_structure.py b/tests/test_file_structure.py new file mode 100644 index 0000000..68d8549 --- /dev/null +++ b/tests/test_file_structure.py @@ -0,0 +1,111 @@ +"""Test suite for validating the expected project file structure. + +Verifies that all required files and directories exist, that the +src/components/ directory contains exactly the expected component files, +that package.json references the correct dependencies, and that +src/types.ts contains the expected type definitions. +""" + +import os +from pathlib import Path +from typing import List + +import pytest + +# Root of the repository is one level above the tests/ directory. +ROOT: Path = Path(__file__).resolve().parent.parent + + +# ------------------------------------------------------------------ +# Individual file-existence tests +# ------------------------------------------------------------------ + +EXPECTED_FILES: List[str] = [ + "package.json", + "vite.config.ts", + "tsconfig.json", + "index.html", + "src/main.tsx", + "src/App.tsx", + "src/App.css", + "src/types.ts", + "src/vite-env.d.ts", + "src/components/TodoInput.tsx", + "src/components/TodoItem.tsx", + "src/components/TodoList.tsx", + "RUNNING.md", + "ARCHITECTURE.md", +] + + +@pytest.mark.parametrize("relative_path", EXPECTED_FILES) +def test_file_exists(relative_path: str) -> None: + """Verify that each expected project file exists on disk.""" + full_path: Path = ROOT / relative_path + assert full_path.exists(), f"Expected file not found: {relative_path}" + assert full_path.is_file(), f"Path exists but is not a file: {relative_path}" + + +@pytest.mark.parametrize("relative_path", EXPECTED_FILES) +def test_file_exists_os_path(relative_path: str) -> None: + """Verify file existence using os.path.exists as a secondary check.""" + full_path: str = os.path.join(str(ROOT), relative_path) + assert os.path.exists(full_path), f"os.path.exists failed for: {relative_path}" + + +# ------------------------------------------------------------------ +# Components directory structure +# ------------------------------------------------------------------ + + +def test_components_directory_contains_exactly_three_files() -> None: + """Verify src/components/ contains exactly the 3 expected component files.""" + components_dir: Path = ROOT / "src" / "components" + assert components_dir.exists(), "src/components/ directory does not exist" + assert components_dir.is_dir(), "src/components is not a directory" + + expected_component_files = { + "TodoInput.tsx", + "TodoItem.tsx", + "TodoList.tsx", + } + + actual_files = {f.name for f in components_dir.iterdir() if f.is_file()} + + assert actual_files == expected_component_files, ( + f"Expected exactly {expected_component_files} in src/components/, " + f"but found {actual_files}" + ) + + +# ------------------------------------------------------------------ +# package.json content validation +# ------------------------------------------------------------------ + + +def test_package_json_contains_required_dependencies() -> None: + """Verify package.json contains 'react', 'typescript', and 'vite' as substrings.""" + package_json_path: Path = ROOT / "package.json" + assert package_json_path.exists(), "package.json does not exist" + + content: str = package_json_path.read_text(encoding="utf-8") + + assert "react" in content, "package.json does not contain 'react'" + assert "typescript" in content, "package.json does not contain 'typescript'" + assert "vite" in content, "package.json does not contain 'vite'" + + +# ------------------------------------------------------------------ +# src/types.ts content validation +# ------------------------------------------------------------------ + + +def test_types_ts_contains_todo_and_completed() -> None: + """Verify src/types.ts contains 'Todo' and 'completed'.""" + types_path: Path = ROOT / "src" / "types.ts" + assert types_path.exists(), "src/types.ts does not exist" + + content: str = types_path.read_text(encoding="utf-8") + + assert "Todo" in content, "src/types.ts does not contain 'Todo'" + assert "completed" in content, "src/types.ts does not contain 'completed'" diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..a4c834a --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,20 @@ +{ + "compilerOptions": { + "target": "ES2020", + "useDefineForClassFields": true, + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "module": "ESNext", + "skipLibCheck": true, + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react-jsx", + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true + }, + "include": ["src"] +} diff --git a/vite.config.ts b/vite.config.ts new file mode 100644 index 0000000..6d7a027 --- /dev/null +++ b/vite.config.ts @@ -0,0 +1,10 @@ +import { defineConfig } from 'vite'; +import react from '@vitejs/plugin-react'; + +export default defineConfig({ + plugins: [react()], + server: { + port: 5173, + host: true, + }, +});