Skip to content
Open
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
398 changes: 398 additions & 0 deletions docs/design/notes-todo-split.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,398 @@
# DESIGN: Split Notes and Todo into Separate Apps (#1923)

## Summary

Split the current shared Notes/Todo implementation into two fully independent
apps with distinct data models, API surfaces, and frontend components.

**Current state:** One component (`DocsApp` in `NotesApp.tsx`) serving two app
IDs (`notes`, `todo`) via config switching (`NOTES_CONFIG` / `TODO_CONFIG`).
One API (`/api/notes`) over one store (`SharedDocsStore`) differentiated by
`kind` field (`note` vs `list`).

**Target state:** `NotesApp.tsx` + `/api/notes` + `NotesStore` for free-text
notes. `TodoApp.tsx` + `/api/todo` + `TodoStore` for checklist-oriented lists
with ordering, completion, and optional due/reminder dates.

---

## 1. Storage Design

### Decision: Split into separate stores

**Rationale:** The two use cases diverge enough that a single schema stretched
to cover both would be awkward. Todo needs per-item ordering (`position`),
completion tracking, and future due-date/reminder support. Notes will evolve
toward rich text, diagrams, and block-based content. Keeping them in one table
forces every Notes row to carry unused `done`/`position`/`due_at` columns and
every Todo row to carry block-type metadata it won't use.

The shared collaboration model (members, permissions, agent triggers, revision
history) will be **extracted into a shared base** rather than duplicated.

### 1a. TodoStore (new)

Dedicated SQLite store at `tinyagentos/todo/todo_store.py`. Schema:

```sql
CREATE TABLE todo_lists (
id TEXT PRIMARY KEY,
owner_user_id TEXT NOT NULL,
title TEXT NOT NULL DEFAULT '',
created_at REAL NOT NULL,
updated_at REAL NOT NULL,
archived_at REAL
);

CREATE TABLE todo_items (
id TEXT PRIMARY KEY,
list_id TEXT NOT NULL REFERENCES todo_lists(id),
text TEXT NOT NULL DEFAULT '',
done INTEGER NOT NULL DEFAULT 0,
position INTEGER NOT NULL DEFAULT 0,
due_at REAL, -- optional deadline
remind_at REAL, -- optional reminder timestamp
author TEXT NOT NULL DEFAULT '',
created_at REAL NOT NULL,
updated_at REAL NOT NULL
);
CREATE INDEX idx_todo_items_list ON todo_items(list_id, position);

-- Share the existing member + revision tables from SharedDocsStore
-- via a shared_collaboration module (see 1c).
```

Key differences from current shared_doc_entries:
- `position` column enables drag-to-reorder
- `due_at` / `remind_at` for future due-date/reminder features
- `updated_at` tracked per-item (needed for sync/last-modified display)
- No generic `kind` column — every row is a todo item

### 1b. NotesStore (refactored from SharedDocsStore)

Keep `tinyagentos/notes/shared_docs_store.py` but rename/refactor:

- Remove `kind` column from `shared_docs` — all docs are notes now
- Remove `done` from `shared_doc_entries` — notes don't have completion
- Rename `shared_doc_entries` → `note_blocks` (optional; can defer)
- The existing entry revision history is preserved **via the shared
collaboration module** (see 1c) — it's useful for both apps without
duplication

Schema (existing, minus `kind` and `done`):

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SUGGESTION: Inconsistent description of where revision history lives after the split.

§1c says member/permission/revision logic is extracted into tinyagentos/collaboration/ (line 104), but §1b says NotesStore keeps shared_docs_store.py with the entry revision history "preserved as-is" (lines 78–79) and the schema block comments "Member + revision tables shared via shared_collaboration module" (line 101). It's unclear whether the revision tables are moved out of the notes store into the shared module (requiring refactor + migration of existing revision data) or left in place. Clarify, since moving shared tables impacts the migration plan and existing data.


Reply with @kilocode-bot fix it to have Kilo Code address this issue.


```sql
CREATE TABLE notes (
id TEXT PRIMARY KEY,
owner_user_id TEXT NOT NULL,
title TEXT NOT NULL DEFAULT '',
created_at REAL NOT NULL,
updated_at REAL NOT NULL,
archived_at REAL
);

CREATE TABLE note_entries (
id TEXT PRIMARY KEY,
note_id TEXT NOT NULL REFERENCES notes(id),
text TEXT NOT NULL DEFAULT '',
author TEXT NOT NULL DEFAULT '',
created_at REAL NOT NULL
);

-- Member + revision tables shared via shared_collaboration module
```

### 1c. Shared Collaboration Module (extracted, not duplicated)

Both stores use identical member/permission/revision patterns. Extract these
into a reusable module at `tinyagentos/collaboration/`:

```
tinyagentos/collaboration/
__init__.py
member_store.py -- member CRUD, permission check, discuss_channel
revision_store.py -- immutable revision log with diff/snapshot
constants.py -- VALID_PERMISSIONS, VALID_ACTIONS
Comment on lines +110 to +115

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Add languages to the fenced tree blocks.

The collaboration directory tree and frontend component tree use unlabeled fenced code blocks. Mark them as text (or another appropriate language) to resolve Markdownlint MD040.

Also applies to: 231-237

🧰 Tools
🪛 markdownlint-cli2 (0.23.0)

[warning] 110-110: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/design/notes-todo-split.md` around lines 110 - 115, Update the fenced
code blocks containing the collaboration directory tree and frontend component
tree in the design notes to specify the text language, preserving their tree
content and formatting while resolving Markdownlint MD040.

Source: Linters/SAST tools

```

Each store (`todo_store.py`, `notes_store.py`) composes the collaboration
tables into its own schema string and delegates member/revision operations.

**Why not one generic store:** The todo and notes row shapes differ enough
(`position`, `due_at` vs no such columns) that a single "generic doc" table
would require nullable-everything columns and runtime kind-checking. Separate
stores with shared collaboration plumbing give us type-safe schemas without
duplicating the member/revision logic.

---

## 2. API Surface

### 2a. Todo API (`/api/todo`)

New route module at `tinyagentos/routes/todo.py`, registered as an APIRouter
in `create_app()`.

| Method | Path | Purpose |
|--------|------|---------|
| GET | `/api/todo` | List user's todo lists (non-archived) |
| POST | `/api/todo` | Create a new todo list |
| GET | `/api/todo/{list_id}` | Get list with items (ordered by position) |
| PATCH | `/api/todo/{list_id}` | Rename or archive list |
| POST | `/api/todo/{list_id}/items` | Add item (appended at end) |
| PATCH | `/api/todo/{list_id}/items/{item_id}` | Toggle done, set due date, edit text |
| DELETE | `/api/todo/{list_id}/items/{item_id}` | Delete item |
| PUT | `/api/todo/{list_id}/items/reorder` | Batch reorder: `{items: [{id, position}, ...]}` |
| GET | `/api/todo/{list_id}/members` | List members |
| POST | `/api/todo/{list_id}/members` | Add member (user or agent) |
| DELETE | `/api/todo/{list_id}/members/{type}/{id}` | Remove member |
Comment on lines +136 to +148

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | 🏗️ Heavy lift

Specify authorization and ID scoping for Todo routes.

The API contract does not define owner/member permissions or require item_id to belong to list_id. Existing tinyagentos/tools/notes_tools.py Lines 81-129 explicitly checks membership, permission, archive state, and entry ownership; C1 should define equivalent checks for every Todo read/write/reorder endpoint.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/design/notes-todo-split.md` around lines 136 - 148, Update the Todo API
contract table and its surrounding C1 specification to define authorization for
every listed route, including owner/member permissions and archived-list
behavior, and require item_id/list_id scoping validation on item operations and
reorder requests. Align these checks with the membership, permission,
archive-state, and entry-ownership checks used by notes_tools.py, covering
reads, writes, membership changes, and reordering.


Pydantic models:

```python
class CreateTodoListIn(BaseModel):
title: str = ""

class PatchTodoListIn(BaseModel):
title: str | None = None
archived: bool | None = None

class AddTodoItemIn(BaseModel):
text: str
due_at: str | None = None # ISO-8601 timestamp

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SUGGESTION: ISO-8601 string → REAL timestamp conversion is unspecified.

AddTodoItemIn/PatchTodoItemIn model due_at/remind_at as str | None ISO-8601 (lines 161–162, 167–168), but the todo_items schema stores them as REAL (lines 53–54). The design never states where/how the ISO string is parsed and validated into a REAL epoch, nor what happens on malformed input. Specify the conversion point (Pydantic validator vs router) and the error/validation behavior to avoid silent bad data.


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

remind_at: str | None = None # ISO-8601 timestamp

class PatchTodoItemIn(BaseModel):
text: str | None = None
done: bool | None = None
due_at: str | None = None
remind_at: str | None = None

class ReorderItemsIn(BaseModel):
items: list[ReorderEntry]

class ReorderEntry(BaseModel):
id: str
position: int
```

**Reorder contract:** The reorder endpoint receives a complete ordering of
every item in the list — each `id` must appear exactly once, `position`

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SUGGESTION: Reorder contract leaves two edge cases unspecified.

  1. The contract rejects payloads containing "missing items," but if a client sends a stale snapshot (e.g. an item was added concurrently by another user), the entire reorder is rejected with 400. Consider documenting optimistic-concurrency behavior or that the payload is validated only against the ids it contains.
  2. It requires "unique positions (no ties)" but doesn't state whether positions must be a contiguous 0..N-1 range or merely distinct integers. A store that re-normalizes to contiguous positions vs one that stores arbitrary unique ints will behave differently on later inserts. Specify the expected range so PUT reorder and POST items (appended at end) stay consistent.

Reply with @kilocode-bot fix it to have Kilo Code address this issue.

values must be unique (no ties), and the entire batch is applied atomically
within a single transaction. Partial or invalid payloads (missing items,
duplicate positions, unknown ids) are rejected with `400 Bad Request` and
make no changes to the stored order.

**Timestamp conversion:** `due_at` and `remind_at` arrive as ISO-8601
strings (e.g. `"2026-07-18T14:00:00Z"`) from the API. The route handler
converts them to SQLite REAL (Unix epoch seconds) via
`datetime.fromisoformat()` (Python ≥ 3.11, which this project requires,
handles the `Z` suffix natively as UTC). The store writes
the float timestamp directly into the `REAL` column, matching the existing
`created_at`/`updated_at` convention used throughout the codebase. On read,
the store returns the float; the route serializes it back to ISO-8601 for
the response model.

### 2b. Notes API (`/api/notes`)

Keep existing routes at `/api/notes` with minimal changes:

- Remove `kind` from `CreateDocIn` (all docs are notes now)
- Remove `PatchEntryIn.done` (notes don't have completion)
- Entry history endpoints preserved as-is
- Member endpoints preserved as-is

```python
class CreateNoteIn(BaseModel): # was CreateDocIn
title: str = ""

class AddEntryIn(BaseModel): # unchanged
text: str

class EditEntryTextIn(BaseModel): # unchanged
text: str
```

### 2c. Agent tools

| Current Tool | After (rename) | Purpose |
|------|---------|-------|
| `notes_list_shared_docs` | → `notes_list_docs` | Lists the agent's notes |
| `notes_add_entry` | → `notes_add_entry` | Appends to a note |
| `notes_set_done` | → **removed** from notes | Notes don't have done |
| (new) `todo_list_lists` | — | Lists the agent's todo lists |
| (new) `todo_add_item` | — | Appends a todo item |
| (new) `todo_set_done` | — | Toggles completion on a todo item |

---

## 3. Component Split

### 3a. Current: One component, two configs

`NotesApp.tsx` (1177 lines) exports both `NotesApp` and `TodoApp`. They share
`DocsApp`, `NoteDetailPane`, `EntryRow`, `SharePanel`, `EntryHistoryPanel`,
and `CreateNoteForm` — differentiated only by the `DocKindConfig` object.

### 3b. Target: Two separate component trees

```

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SUGGESTION: The target component tree lists the same test files in two locations.

NotesApp.test.tsx and TodoApp.test.tsx appear both under desktop/src/apps/ (lines 224, 226) and again under desktop/src/apps/apps/__tests__/ (lines 228–229). This is contradictory — it's unclear whether tests live at the app root or in a nested __tests__/ dir. Pick one convention (the nested apps/__tests__/ path also looks like a typo, duplicating apps/).


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

desktop/src/apps/
NotesApp.tsx ← free-text notes component
NotesApp.test.tsx
TodoApp.tsx ← checklist-oriented todo component
TodoApp.test.tsx
```

**Shared UI primitives extracted to `desktop/src/apps/notes-shared/`:**

- `MemberBadge.tsx` — unchanged
- `SharePanel.tsx` — shared via members API (different endpoint per app)
- `EntryHistoryPanel.tsx` — revision viewer, unchanged internally

**NotesApp specifics:**
- No done checkboxes
- No reorder drag handles
- Textarea-based entry input (existing)
- Future: rich text editor, diagram canvas, link embeds

**TodoApp specifics:**
- Checkbox per item (Square/CheckSquare, optimistic toggle)
- Drag handles for reorder (future: `@dnd-kit` or native HTML5 drag)
- Due date display + date picker
- "Add task" input bar (single-line + Enter to add)
- Visual separation: completed items moved to bottom or collapsible section

### 3c. App registry

No changes to `app-registry.ts` needed — the two app IDs (`notes`, `todo`)
already exist and will continue to lazy-import their respective components:

```typescript
// Before (both from same chunk):
{ id: "notes", component: () => import("@/apps/NotesApp").then(m => ({ default: m.NotesApp })) }
{ id: "todo", component: () => import("@/apps/NotesApp").then(m => ({ default: m.TodoApp })) }

// After (separate chunks):
{ id: "notes", component: () => import("@/apps/NotesApp").then(m => ({ default: m.NotesApp })) }
{ id: "todo", component: () => import("@/apps/TodoApp").then(m => ({ default: m.TodoApp })) }
Comment on lines +268 to +278

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Resolve the app-registry contradiction.

Line 260 says no registry changes are needed, but the target block changes the Todo lazy import from NotesApp to TodoApp, and C2 explicitly requires updating the imports. Revise the “No changes” statement so the implementation does not omit this routing change.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/design/notes-todo-split.md` around lines 260 - 270, Revise the
app-registry guidance to explicitly require updating the `todo` entry’s lazy
import from `@/apps/NotesApp` to `@/apps/TodoApp`, while leaving the existing
`notes` entry unchanged. Remove or correct the statement that no
`app-registry.ts` changes are needed.

```

---

## 4. Migration Plan

### 4a. Database migration

A one-time migration script at `tinyagentos/migrations/migrate_notes_todo_split.py`
(or as a store `_post_init` migration):

**Step 1:** Create `todo_lists` and `todo_items` tables.

**Step 2:** Copy existing `shared_docs` with `kind = 'list'` into `todo_lists`:
```sql
INSERT INTO todo_lists (id, owner_user_id, title, created_at, updated_at, archived_at)
SELECT id, owner_user_id, title, created_at, updated_at, archived_at
FROM shared_docs WHERE kind = 'list';
```

**Step 3:** Copy entries:
```sql
INSERT INTO todo_items (id, list_id, text, done, position, author, created_at, updated_at)
SELECT e.id, e.doc_id, e.text, e.done,
(SELECT COUNT(*) FROM shared_doc_entries e2
WHERE e2.doc_id = e.doc_id AND e2.created_at <= e.created_at) - 1,
e.author, e.created_at, e.created_at
FROM shared_doc_entries e
JOIN shared_docs d ON d.id = e.doc_id
WHERE d.kind = 'list';
Comment on lines +287 to +308

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

Make the migration actually idempotent.

The PR objective requires an idempotent migration, but table creation and both INSERT statements are described as one-shot operations. A rerun after a crash or partial completion can hit the primary keys on todo_lists and todo_items; member migration is also unspecified. Define transaction/checkpoint behavior and explicit conflict handling for every step.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/design/notes-todo-split.md` around lines 279 - 300, Update the migration
described in migrate_notes_todo_split.py to be safely rerunnable: wrap each
migration phase in a transaction or durable checkpoint, make table creation
non-failing when tables already exist, and add explicit conflict handling to
both todo_lists and todo_items inserts. Also define how membership data is
migrated and checkpointed so crashes or partial completion can resume without
duplicate rows or lost members.

```

`position` is derived from `created_at` order — items retain their existing
chronological order after migration.
Comment on lines +301 to +312

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Use a deterministic tie-breaker when deriving positions.

The COUNT(... created_at <= e.created_at) expression assigns the same position to entries with equal timestamps. Since Todo reads depend on position, migrated items can have nondeterministic order. Add a stable secondary key, such as id, to the ordering and position calculation.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/design/notes-todo-split.md` around lines 293 - 304, Update the position
calculation in the shared_doc_entries migration INSERT to use a deterministic
ordering by created_at and a stable secondary key such as id. Ensure the
correlated COUNT for each entry counts rows preceding or equal to it under that
same composite order, producing unique positions while preserving chronological
ordering.


**Step 4:** Copy members for migrated lists into new todo_members table.

**Step 5:** Do NOT delete the source data — leave `shared_docs` rows in place
as a safety net. The migration is additive. A future cleanup PR can remove
kind=list rows after the split is stable.

**Step 6:** Remove `kind` column from `shared_docs` (or set all remaining to
`note`). Drop the `done` column from `shared_doc_entries` after confirming
no Todo consumers remain on the old tables.
Comment on lines +314 to +322

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

Align collaboration-data migration with the storage design.

The target schema says member and revision data are provided by shared_collaboration, while this migration introduces todo_members and omits revision-history migration entirely. Define whether collaboration rows remain in one shared database or are copied into TodoStore; if copied, migrate both members and revisions while preserving IDs and references.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/design/notes-todo-split.md` around lines 306 - 314, Update the migration
plan around Steps 4–6 to match the storage design: explicitly state whether
collaboration data remains in the shared database or is copied into TodoStore.
If copied, replace the todo_members-only migration with migration of both member
and revision rows, preserving their IDs and all references, and include the
required source/target table handling.

Comment on lines +316 to +322

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

Do not drop kind while retaining list source rows.

Step 5 preserves kind='list' rows as a safety net, but Step 6 says to remove kind or rewrite remaining rows to note. Rewriting migrated Todo rows as notes misclassifies the retained source data, while dropping the column removes the distinction. Define a cleanup order that deletes or archives legacy list rows before removing kind.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/design/notes-todo-split.md` around lines 308 - 314, Revise Step 6 in the
migration plan so legacy shared_docs rows with kind='list' are first deleted or
archived after confirming the split is stable. Only then remove the kind column
or normalize remaining rows to note, preserving the distinction for retained
source data until cleanup is complete.


### 4b. Rollout strategy

1. **Phase 1 (C1):** Backend split — `TodoStore`, `routes/todo.py`, migration
script. Old `/api/notes` still serves both kinds during transition.
2. **Phase 2 (C2):** Frontend split — `TodoApp.tsx` hits new `/api/todo`
endpoints. `NotesApp.tsx` hits `/api/notes`. Both apps fully functional.
3. **Phase 3 (C3):** Agent tools — `todo_list_lists`, `todo_add_item`,
`todo_set_done`. Remove `notes_set_done`.
4. **Phase 4 (C4):** Cleanup — remove `kind` from Notes API, drop kind=list
support from `/api/notes`, remove old `shared_doc_entries.done` column.
Comment on lines +330 to +333

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Make notes_set_done removal timing consistent.

Phase 3 and C3 remove notes_set_done, while the risk mitigation says the old tool remains registered until C4. Choose one cutover point and document the compatibility behavior; otherwise agents may lose the existing tool before the final cleanup phase.

Also applies to: 366-370, 388-390

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/design/notes-todo-split.md` around lines 322 - 325, Align the
`notes_set_done` removal timing across the Phase 3/C3 plan and the related
risk-mitigation sections. Choose a single cutover point, document whether the
existing tool remains registered as a compatibility path until that point, and
ensure all references consistently describe the same behavior through C4.


---

## 5. App Registry Changes

Minimal — the two app IDs (`notes`, `todo`) remain but now point to separate
component chunks:

```typescript
{ id: "notes", name: "Notes", icon: "sticky-note", category: "platform",
component: () => import("@/apps/NotesApp").then(m => ({ default: m.NotesApp })),
defaultSize: { w: 860, h: 620 }, minSize: { w: 520, h: 400 },
singleton: true, pinned: false, launchpadOrder: 16.8 },

{ id: "todo", name: "Todo", icon: "list-checks", category: "platform",
component: () => import("@/apps/TodoApp").then(m => ({ default: m.TodoApp })),
defaultSize: { w: 860, h: 620 }, minSize: { w: 520, h: 400 },
singleton: true, pinned: false, launchpadOrder: 16.85 },
```

---

## 6. Child Task Breakdown (C1–C4)

### C1: Backend — TodoStore + routes/todo.py + migration
**Assignee:** `skald-engineer-taos`
- Create `tinyagentos/todo/todo_store.py` with TodoStore (BaseStore subclass)
- Create `tinyagentos/routes/todo.py` with full CRUD + reorder endpoints
- Register router in `app.py`'s `create_app()`
- Create migration script or `_post_init` migration
- Write `tests/todo/test_todo_routes.py`

### C2: Frontend — TodoApp.tsx + NotesApp refactor
**Assignee:** `skald-engineer-taos`
- Create `desktop/src/apps/TodoApp.tsx` (checklist UI, drag handles, due dates)
- Extract shared primitives to `desktop/src/apps/notes-shared/`
- Refactor `NotesApp.tsx` to remove Todo config, keep Notes only
- Update app-registry imports
- Write/update tests

### C3: Agent tools — todo tools + notes cleanup
**Assignee:** `skald-engineer-taos`
- Create `tinyagentos/tools/todo_tools.py` with `todo_list_lists`, `todo_add_item`, `todo_set_done`
- Remove `notes_set_done` from `notes_tools.py`
- Register new tools in tool registry
- Write `tests/todo/test_todo_tools.py`

### C4: Cleanup — remove kind, drop done column, final consolidation
**Assignee:** `skald-engineer-taos`
- Remove `kind` from Notes API (CreateDocIn, routes, store)
- Drop `done` column from `shared_doc_entries` (schema migration)
- Remove kind=list filtering from Notes frontend
- Update integration tests
- Run full test gate

---

## 7. Risks and Mitigations

| Risk | Mitigation |
|------|------------|
| Migration breaks existing lists | Additive-only migration; old data preserved; both APIs run side-by-side during transition |
| Frontend regressions | Separate test files for each app; existing tests pass before merge |
| Agent tools silently break | Tool registry registration is atomic; old tool stays registered until C4 explicitly removes it |
| Duplicated collaboration logic | Extracted to shared `collaboration/` module in C1; both stores compose it |
Loading