Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
120 changes: 120 additions & 0 deletions ARCHITECTURE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
# Architecture — Mini React Todo App

This document describes the complete architecture of the Mini React Todo App,
a single-page application built with React, TypeScript, and Vite.

## Overview

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 data model is defined in `src/types.ts`:

```typescript
export interface Todo {
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`): User-supplied description of the task.
- **completed** (`boolean`): Toggled between `true` and `false`.

## Component Tree

```
App
├── TodoInput
└── TodoList
└── TodoItem (one per todo)
```

- **App** → **TodoInput**: passes `addTodo` callback.
- **App** → **TodoList**: passes `todos`, `toggleTodo`, and `deleteTodo`.
- **TodoList** → **TodoItem**: passes individual `todo`, `toggleTodo`, and `deleteTodo`.

## Component Responsibilities

### App (`src/App.tsx`)

- Owns application state via `useState<Todo[]>`.
- 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 `<TodoInput>` and `<TodoList>` with appropriate props.

### TodoInput (`src/components/TodoInput.tsx`)

- Maintains local `useState<string>` for the input field value.
- On form submission:
- Trims whitespace; if the result is empty, does **not** call `addTodo`.
- Otherwise calls `addTodo(trimmedText)` and clears the input.
- Props: `{ addTodo: (text: string) => void }`.

### TodoList (`src/components/TodoList.tsx`)

- Receives the full `todos` array, `toggleTodo`, and `deleteTodo` as props.
- Maps over `todos` and renders a `<TodoItem>` 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 }`.

### TodoItem (`src/components/TodoItem.tsx`)

- 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 }`.

## Data Flow

The application follows a strict **unidirectional data flow**:

1. **State** lives exclusively in `App` via `useState<Todo[]>`.
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.

No child component mutates state directly.

## State Management

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.

## File Structure

```
/
├── 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

- **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.
46 changes: 32 additions & 14 deletions RUNNING.md
Original file line number Diff line number Diff line change
@@ -1,33 +1,51 @@
# Running the Todo API
# Running the Mini React Todo App

## Prerequisites

- Python 3.10 or later
- Node.js >= 18
- npm >= 9
- Python >= 3.9 (for running the test suite)

## Install dependencies
## Quick Start

```bash
pip install fastapi uvicorn pydantic
# Install dependencies
npm install

# Start the Vite dev server
npm run dev
```

For running the test suite you will also need:
The application will be available at **http://localhost:5173**.

## Running Tests

```bash
pip install httpx pytest
# Install Python test dependencies
pip install pytest

# Run the test suite
pytest tests/
```

## Start the server
## Docker (optional)

```bash
uvicorn main:app --reload --host 0.0.0.0 --port 8000
docker compose up --build
```

The API will be available at <http://localhost:8000>.
Open **http://localhost:5173** in your browser.

Interactive docs are served at <http://localhost:8000/docs>.
## Notes

## Run the tests
- No authentication is required.
- No demo credentials needed.
- All state is in-memory (browser only); refreshing the page clears todos.

```bash
pytest tests/
```
## TEAM_BRIEF
stack: TypeScript/React+Vite
test_runner: pytest tests/
lint_tool: none
coverage_tool: none
coverage_threshold: 0
coverage_applies: false
12 changes: 12 additions & 0 deletions index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Mini React Todo App</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>
22 changes: 22 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -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"
}
}
104 changes: 104 additions & 0 deletions src/App.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
/* Application styles for the Mini React Todo App */

* {
box-sizing: border-box;
margin: 0;
padding: 0;
}

body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto,
Oxygen, Ubuntu, Cantarell, sans-serif;
background-color: #f5f5f5;
color: #333;
}

.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;
color: #1a1a2e;
}

.todo-input-form {
display: flex;
gap: 0.5rem;
margin-bottom: 1.5rem;
}

.todo-input-form input {
flex: 1;
padding: 0.5rem 0.75rem;
border: 1px solid #ccc;
border-radius: 4px;
font-size: 1rem;
}

.todo-input-form button {
padding: 0.5rem 1rem;
background-color: #1a1a2e;
color: #fff;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 1rem;
}

.todo-input-form button:hover {
background-color: #16213e;
}

.todo-list-empty {
text-align: center;
color: #999;
padding: 1rem 0;
}

.todo-item {
display: flex;
align-items: center;
gap: 0.75rem;
padding: 0.75rem 0;
border-bottom: 1px solid #eee;
}

.todo-item:last-child {
border-bottom: none;
}

.todo-item input[type='checkbox'] {
width: 1.2rem;
height: 1.2rem;
cursor: pointer;
}

.todo-item .todo-text {
flex: 1;
font-size: 1rem;
}

.todo-item .todo-text.completed {
text-decoration: line-through;
color: #999;
}

.todo-item .delete-btn {
background: none;
border: none;
color: #e74c3c;
cursor: pointer;
font-size: 1.1rem;
padding: 0.25rem 0.5rem;
}

.todo-item .delete-btn:hover {
color: #c0392b;
}
46 changes: 46 additions & 0 deletions src/App.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import React, { useState } from 'react';
import { Todo } from './types';
import TodoInput from './components/TodoInput';
import TodoList from './components/TodoList';
import './App.css';

/**
* Root application component.
*
* Owns the todo list state and provides addTodo, toggleTodo, and
* deleteTodo callbacks to child components.
*/
const App: React.FC = () => {
const [todos, setTodos] = useState<Todo[]>([]);

const addTodo = (text: string): void => {
const newTodo: Todo = {
id: crypto.randomUUID(),
text,
completed: false,
};
setTodos((prev) => [newTodo, ...prev]);
};

const toggleTodo = (id: string): void => {
setTodos((prev) =>
prev.map((todo) =>
todo.id === id ? { ...todo, completed: !todo.completed } : todo,
),
);
};

const deleteTodo = (id: string): void => {
setTodos((prev) => prev.filter((todo) => todo.id !== id));
};

return (
<div className="app">
<h1>Todo App</h1>
<TodoInput addTodo={addTodo} />
<TodoList todos={todos} toggleTodo={toggleTodo} deleteTodo={deleteTodo} />
</div>
);
};

export default App;
Loading