Files
Calendink/Provider/tdd/todo_list.md

165 lines
9.2 KiB
Markdown

# Todo List System for ESP32-S3 Provider
**Authored by Antigravity (Claude Opus)**
**Date:** 2026-03-07
---
## 1. Goal
Add a user-managed todo list system to the Calendink Provider. The system must:
- Allow creating, modifying, and deleting **users** via a REST API.
- Allow creating, modifying, and deleting **tasks** per user, each with a due date.
- Display the **top 3 upcoming tasks** per user on the Dashboard home page.
- Introduce a **collapsible sidebar** to navigate between Dashboard and a detailed Task Manager view.
- Store everything **in RAM only** — all data is lost on reboot. Persistent storage (SQLite on SD card) is planned as a future phase.
- Pre-populate **seed data** on boot so the UI is immediately usable during development without manual setup after every reboot.
## 2. Chosen Approach
### 2.1. Backend: Static Arrays in BSS
Users and tasks are stored as **fixed-size static arrays** at file scope (BSS segment). This means:
- No `malloc` / `free` — zero fragmentation risk on a constrained device.
- Deterministic memory footprint: the arrays consume a fixed amount of RAM regardless of usage.
- Simple slot-based allocation: each entry has an `active` flag; adding an item finds the first inactive slot, deleting clears the flag.
Limits: `MAX_USERS = 8`, `MAX_TASKS = 32` (total across all users). At ~80 bytes per task and ~40 bytes per user, this uses <3KB of RAM — negligible against the ~247KB free heap the system typically has. These limits will become irrelevant when we migrate to SQLite.
### 2.2. Frontend: Sidebar + View Routing
The existing single-page dashboard is extended with a collapsible left sidebar. The `App.svelte` component gains a view state (`'dashboard' | 'tasks'`) and conditionally renders either the existing Dashboard content or a new `TodoManager` component. This avoids introducing a full client-side router for what is currently a two-view app.
### 2.3. API Style: RESTful with DELETE
We use standard REST conventions: `GET` for reads, `POST` for creates/updates, `DELETE` for removals. This requires updating the existing CORS handler to allow the `DELETE` method. We chose REST purity over the simpler "POST everything" approach because the API surface is small enough that the CORS overhead is negligible, and it sets the right pattern for future endpoint growth.
## 3. Design Decisions & Trade-offs
### 3.1. Why Static Arrays Over Heap Allocation?
| | Static Arrays (BSS) | Dynamic (malloc/vector) |
|---|---|---|
| **Fragmentation** | ✅ Impossible | ⚠️ Risk on long-running device |
| **Memory footprint** | Fixed, predictable | Grows/shrinks at runtime |
| **Complexity** | Trivial — array index + active flag | Requires careful lifetime management |
| **Scalability** | Hard limit (32 tasks) | Flexible |
| **Migration path** | Replace array access with SQLite queries | Same |
For a temporary in-memory store that will be replaced by SQLite, static arrays are the simpler and safer choice. The hard limits are acceptable given the short-lived nature of this storage layer.
### 3.2. Why a Dedicated `/api/tasks/upcoming` Endpoint?
The Dashboard needs to show the top 3 tasks per user, sorted by due date. Two options were considered:
1. **Client-side**: Fetch all tasks for all users, sort and slice on the frontend.
2. **Server-side**: Dedicated endpoint that returns pre-sorted, pre-sliced data.
We chose option 2 because:
- It reduces the number of API calls (1 instead of N per user).
- Sorting and filtering on the ESP32 is trivial for 32 items.
- The response payload is smaller — only the data the Dashboard actually needs.
### 3.3. Seed Data Strategy
Since all data is lost on reboot, manually re-creating users and tasks after every firmware flash would slow down development. Both `user.hpp` and `todo.hpp` expose `seed_users()` / `seed_tasks()` functions called at server startup. The seed tasks use **relative time offsets** from the current system time (e.g., "now + 1 day", "now + 3 days"), so due dates always appear realistic regardless of when the device boots.
### 3.4. Frontend Navigation: Sidebar vs. Tabs vs. Router
| | Sidebar | Tabs | Client-side Router |
|---|---|---|---|
| **Scalability** | ✅ Easily add more views | Limited horizontal space | Best for many routes |
| **Discoverability** | Always visible (or icon-only when collapsed) | Always visible | Requires URL navigation |
| **Complexity** | Low — simple boolean state | Low | Higher — routing library needed |
| **Mobile** | Collapsible — works well | Good | Good |
A sidebar is the best fit: it scales to future views (Calendar, Settings, etc.), gives a clear navigation model, and the collapsible behavior keeps it unobtrusive on smaller screens.
### 3.5. `max_uri_handlers` Increase
The ESP-IDF HTTP server has a compile-time limit on registered URI handlers (default: 8, currently set to 10). Adding 7 new endpoints (3 user + 4 task) brings the total to ~17. We increase the limit to 20 to accommodate the new routes with room for growth. Each handler slot costs minimal memory (~40 bytes).
## 4. Architecture
### 4.1. Data Models
```
user.hpp todo.hpp
┌─────────────────────┐ ┌──────────────────────────┐
│ user_t │ │ task_t │
│ id: uint8 │◄─────── │ user_id: uint8 │
│ name: char[32] │ │ id: uint16 │
│ active: bool │ │ title: char[64] │
└─────────────────────┘ │ due_date: int64 (epoch)│
g_Users[MAX_USERS=8] │ completed: bool │
│ active: bool │
└──────────────────────────┘
g_Tasks[MAX_TASKS=32]
```
### 4.2. API Endpoints
| Method | URI | Purpose |
|--------|-----|---------|
| `GET` | `/api/users` | List all active users |
| `POST` | `/api/users` | Create a user |
| `DELETE` | `/api/users?id=N` | Delete a user + cascade-delete their tasks |
| `GET` | `/api/tasks?user_id=N` | List tasks for a user |
| `GET` | `/api/tasks/upcoming` | Top 3 upcoming tasks per user (Dashboard) |
| `POST` | `/api/tasks` | Create a task |
| `POST` | `/api/tasks/update` | Modify a task (title, due date, completion) |
| `DELETE` | `/api/tasks?id=N` | Delete a task |
### 4.3. Backend File Structure
```
Provider/main/
├── user.hpp # user_t struct, g_Users[] (data only)
├── todo.hpp # task_t struct, g_Tasks[] (data only)
├── api/
│ ├── users/
│ │ ├── store.hpp # Forward declarations for user operations
│ │ ├── store.cpp # find_user, add_user, remove_user, seed_users
│ │ ├── list.cpp # GET /api/users
│ │ ├── add.cpp # POST /api/users
│ │ ├── remove.cpp # DELETE /api/users
│ │ └── unity.cpp # Includes all users/*.cpp
│ ├── tasks/
│ │ ├── store.hpp # Forward declarations for task operations
│ │ ├── store.cpp # find_task, add_task, remove_task, sort, seed_tasks
│ │ ├── list.cpp # GET /api/tasks?user_id=N
│ │ ├── upcoming.cpp # GET /api/tasks/upcoming
│ │ ├── add.cpp # POST /api/tasks
│ │ ├── update.cpp # POST /api/tasks/update
│ │ ├── remove.cpp # DELETE /api/tasks
│ │ └── unity.cpp # Includes all tasks/*.cpp
│ ├── system/
│ │ ├── info.cpp
│ │ └── reboot.cpp
│ └── ota/
│ └── ...
├── http_server.cpp # includes tasks/unity.cpp + users/unity.cpp
└── main.cpp # Entry point (unchanged)
```
### 4.4. Frontend Component Structure
```
frontend/src/
├── App.svelte # Layout with Sidebar + view routing
├── lib/
│ ├── Sidebar.svelte # Collapsible left nav
│ ├── UserManager.svelte # User selection & management
│ ├── TaskManager.svelte # Full task management UI (embeds UserManager)
│ ├── OTAUpdate.svelte # Existing OTA component
│ └── api.js # +user/task API functions
└── app.css # +sidebar theme tokens
```
## 5. Summary
We implement a **temporary in-memory todo list** using **static BSS arrays** on the ESP32 backend, exposed via a **RESTful API** with 8 new endpoints. The frontend gains a **collapsible sidebar** for navigation between the existing Dashboard and a new **Task Manager** view. **Seed data** is populated on every boot for fast development iteration. The architecture is designed to make the eventual migration to **SQLite on SD card** straightforward — the API contracts and frontend components remain unchanged; only the storage layer swaps out.
---
*Created by Antigravity (Claude Opus) - Last Updated: 2026-03-07*