Problem Statement
The user has no persistent, local task list for tracking work items. Ideas, implementation details, and follow-up items are scattered across Thread transcripts or external tools. A structured, keyboard-accessible todo panel integrated into the T3 Code workspace gives a single place to capture, categorize, reorder, and complete tasks — with rich markdown descriptions that can hold full implementation details.
Solution
A global right sidebar todo panel (toggled via cmd+L) matching the existing diff sidebar pattern. Items live in colored, collapsible categories with drag-to-reorder and drag-to-recategorize in a single gesture. Each item has a three-state status cycle (todo → in-progress → done), a hidden rich markdown description, and optional JIRA links that inherit from the parent category. Completed items move to a bottom "done" section showing the last 10; older items archive to a separate file. Categories are reorderable via drag, created inline, renamed via double-click, and deletable only when empty.
User Stories
- As a user, I want to toggle a todo panel with
cmd+L so I can quickly check or capture tasks without leaving my working context.
- As a user, I want my todo list to be global (not scoped to a single Project) so all my tasks across projects are in one place.
- As a user, I want to organize items into named categories with colors so I can visually group related tasks (e.g. "Backend", "UI", "Bugs").
- As a user, I want categories to render as collapsible sections so I can collapse groups I am not actively working on.
- As a user, I want to drag a category header to reorder categories so I can prioritize groups.
- As a user, I want to create a new category via a subtle "+" button at the top of the list so I can add organizational structure on demand.
- As a user, I want to rename a category by double-clicking its header so I can correct or refine category names.
- As a user, I want to delete a category via right-click context menu so I can remove groups I no longer need — but only when the category has no active items.
- As a user, I want a toggle to show/hide empty categories so the panel stays clean when I have no items in some groups.
- As a user, I want to add a JIRA link to a category (with clickable ticker/number display) so I can associate a category with an epic or board.
- As a user, I want to create new items per category via an icon button inline with the category header so items land in the right group immediately.
- As a user, I want items sorted by creation time by default so I see newest items last (natural reading order).
- As a user, I want to drag any item within its category to reorder it so I can manually prioritize.
- As a user, I want to drag any item into another category to recategorize it in a single gesture so I can reorganize with minimal friction.
- As a user, I want each item to cycle through three statuses (todo → in-progress → done) via a clickable icon so I can track progress.
- As a user, I want items marked as "done" to move immediately to a bottom "Done" section showing the last 10 completed items so I get a sense of recent accomplishment.
- As a user, I want done items to display their original category as a badge/tag so I know where they came from.
- As a user, I want items older than the 10 most recent completions to archive to a separate file so the panel stays fast without losing history.
- As a user, I want to cycle a done item back to "todo" and have it return to its original category so I can resurrect tasks.
- As a user, I want to right-click an item and choose "Delete" with a confirmation dialog so I can permanently remove items I no longer need.
- As a user, I want each item to have a rich markdown description (hidden at a glance) so I can store full implementation details, links, and code snippets.
- As a user, I want to open an item detail by clicking the item row so I can read the rendered markdown description in a side peek panel.
- As a user, I want the side peek panel to extend beyond the sidebar width (not narrow the list or overlay it) so I can see both the list and details on my wide screen.
- As a user, I want to click the rendered markdown to switch to a textarea for editing so I can update the description inline.
- As a user, I want to exit editing mode with Escape and see the rendered markdown again so I can preview my changes.
- As a user, I want each item to optionally have a JIRA link (displayed as clickable ticker/number) so I can link to specific issues.
- As a user, I want items without their own JIRA link to inherit the JIRA link from their category so I only set it once at the group level.
- As a user, I want my todo data persisted to a JSON file so it survives restarts and server updates.
- As a user, I want the category header background tinted with its color, and a lighter version of that color persisting into the background of its items so I can visually scan the list.
- As a user opening the todo panel for the first time, I want a subtle prompt guiding me to create my first category so I am not faced with an empty void.
Implementation Decisions
Data Model
type TodoStatus = "todo" | "in-progress" | "done";
interface TodoCategory {
id: string;
name: string;
color: string;
jiraLink?: string;
order: number;
}
interface TodoItem {
id: string;
categoryId: string;
title: string;
description: string; // markdown
status: "todo" | "in-progress" | "done";
jiraLink?: string;
createdAt: string; // ISO 8601
completedAt?: string; // ISO 8601
order: number;
}
Module Split
todoContracts (packages/contracts): Shared type schemas for items, categories, the archive file shape, and WebSocket message contracts. Schema-only, no runtime logic.
todoStore (packages/shared): Pure state machine handling all todo mutations — CRUD, reorder, recategorize, status cycle, archive cutoff logic, category reorder. Testable with plain objects, no React dependency.
todoPersistence (apps/server): Reads/writes ~/.t3code/todos.json with atomic write semantics. Handles archive spill (items older than position 10 in the done list move to ~/.t3code/todos-archive.json). Debounced writes to avoid thrash.
useTodos hook (apps/web): Bridges todoStore to React via state + WebSocket sync. Exposes actions for the UI.
TodoSidebar (apps/web): Right sidebar shell using the existing Sidebar pattern (SidebarProvider + Sidebar with side="right", collapsible="offcanvas", resizable). Registered via keybinding cmd+L matching the diff sidebar pattern.
TodoCategory (apps/web): Collapsible category header with color tint, inline composer, drag handle for reordering, double-click-to-rename, right-click context menu for delete.
TodoItem (apps/web): Item row with status cycle icon (click toggles todo → in-progress → done → todo), drag handle, title, category badge in done section.
TodoDetail (apps/web): Side peek panel extending beyond the sidebar. Renders markdown via react-markdown + remark-gfm by default. Click into rendered area flips to a textarea for editing. Escape or click-outside returns to rendered view.
State Transitions
- Status cycle:
todo → click → in-progress → click → done → click → todo. Transition to done moves the item to the bottom section. Transition from done to todo returns the item to its original category.
- Done cutoff: The done section displays the 10 most recently completed items (by position). When an 11th item moves to done, the oldest shifts to the archive file. Cycling an older item back from archive is out of scope for v1.
- Category delete guard: A category may only be deleted when it contains zero items with status
todo or in-progress. Items already done or deleted count as empty.
- JIRA inheritance: An item without its own
jiraLink inherits the jiraLink from its category for display purposes. Setting an explicit link on the item overrides the inherited value.
Drag Semantics
A single drag gesture can both reorder within a category and recategorize to another category. @dnd-kit/core + @dnd-kit/sortable (already in apps/web dependencies) handle sortable lists. DndContext wraps the full list; SortableContext wraps each category. Dropping an item into a new category appends it at the dropped position.
WebSocket Contracts
New NativeApi methods on the server WebSocket:
todo.load — returns full { categories: TodoCategory[], items: TodoItem[] }
todo.mutate — accepts a batched mutation object and returns the resulting state. Batched to allow a drag operation (reorder + recategorize) to commit atomically.
Push events on channel todos.changed for live sync if multiple windows are open.
Persistence Strategy
Single JSON file at ~/.t3code/todos.json. Read on startup, written via debounced atomic write (write to temp file, rename over target). Archive file at ~/.t3code/todos-archive.json for items older than the 10 most recent done items. Archive UI is out of scope for v1.
Sidebar Implementation
Matches the diff sidebar pattern exactly: SidebarProvider scoped to the todo panel, Sidebar with side="right", collapsible="offcanvas", resizable prop, controlled by local React state. Keybinding registered via the existing command system (pattern from diff sidebar toggle).
Testing Decisions
Tests should assert external behavior, not implementation details. Focus on state transitions and persistence correctness.
todoStore — Unit tests with plain objects. Test: status cycling, reorder within category, recategorize across categories, archive cutoff logic (item count > 10 in done), category delete guard, JIRA inheritance logic.
todoPersistence — Integration test. Test: round-trip read/write, atomic write (no corruption on crash), archive spill on 11th done item.
UI components (TodoSidebar, TodoCategory, TodoItem, TodoDetail) are tested implicitly via the useTodos hook or left for future browser-based tests.
Prior art: packages/shared already contains unit-testable modules consumed by both server and web.
Out of Scope
- Search/filter of items
- Category item count badges
- Keyboard shortcuts beyond
cmd+L toggle
- Archive file UI (file is written, but not browsable in the panel)
- Multi-window sync conflict resolution (v1 assumes single window; debounced write handles basic safety)
- Rich WYSIWYG markdown editing (live preview while typing); v1 uses read/edit toggle
- JIRA API integration (links are just URLs)
- Category drag between different sections (categories always stay in the header area)
Further Notes
- Drag-and-drop library (
@dnd-kit) and markdown rendering (react-markdown + remark-gfm) are already dependencies in apps/web.
- The diff sidebar provides the exact pattern to copy for the sidebar shell, keybinding registration, and SidebarProvider setup.
- The empty-categories toggle is a simple boolean flag in the store — categories with zero active items are hidden when toggled off. A category with only
done items still counts as empty for the toggle.
- Category color: header background is the full color (with readable text), items within get a lighter variant (opacity or CSS
color-mix to derive from the category color).
Problem Statement
The user has no persistent, local task list for tracking work items. Ideas, implementation details, and follow-up items are scattered across Thread transcripts or external tools. A structured, keyboard-accessible todo panel integrated into the T3 Code workspace gives a single place to capture, categorize, reorder, and complete tasks — with rich markdown descriptions that can hold full implementation details.
Solution
A global right sidebar todo panel (toggled via
cmd+L) matching the existing diff sidebar pattern. Items live in colored, collapsible categories with drag-to-reorder and drag-to-recategorize in a single gesture. Each item has a three-state status cycle (todo → in-progress → done), a hidden rich markdown description, and optional JIRA links that inherit from the parent category. Completed items move to a bottom "done" section showing the last 10; older items archive to a separate file. Categories are reorderable via drag, created inline, renamed via double-click, and deletable only when empty.User Stories
cmd+Lso I can quickly check or capture tasks without leaving my working context.Implementation Decisions
Data Model
Module Split
todoContracts(packages/contracts): Shared type schemas for items, categories, the archive file shape, and WebSocket message contracts. Schema-only, no runtime logic.todoStore(packages/shared): Pure state machine handling all todo mutations — CRUD, reorder, recategorize, status cycle, archive cutoff logic, category reorder. Testable with plain objects, no React dependency.todoPersistence(apps/server): Reads/writes~/.t3code/todos.jsonwith atomic write semantics. Handles archive spill (items older than position 10 in the done list move to~/.t3code/todos-archive.json). Debounced writes to avoid thrash.useTodoshook (apps/web): Bridges todoStore to React via state + WebSocket sync. Exposes actions for the UI.TodoSidebar(apps/web): Right sidebar shell using the existingSidebarpattern (SidebarProvider + Sidebar withside="right",collapsible="offcanvas", resizable). Registered via keybindingcmd+Lmatching the diff sidebar pattern.TodoCategory(apps/web): Collapsible category header with color tint, inline composer, drag handle for reordering, double-click-to-rename, right-click context menu for delete.TodoItem(apps/web): Item row with status cycle icon (click toggles todo → in-progress → done → todo), drag handle, title, category badge in done section.TodoDetail(apps/web): Side peek panel extending beyond the sidebar. Renders markdown viareact-markdown+remark-gfmby default. Click into rendered area flips to a textarea for editing. Escape or click-outside returns to rendered view.State Transitions
todo→ click →in-progress→ click →done→ click →todo. Transition todonemoves the item to the bottom section. Transition fromdonetotodoreturns the item to its original category.todoorin-progress. Items alreadydoneor deleted count as empty.jiraLinkinherits thejiraLinkfrom its category for display purposes. Setting an explicit link on the item overrides the inherited value.Drag Semantics
A single drag gesture can both reorder within a category and recategorize to another category.
@dnd-kit/core+@dnd-kit/sortable(already inapps/webdependencies) handle sortable lists. DndContext wraps the full list; SortableContext wraps each category. Dropping an item into a new category appends it at the dropped position.WebSocket Contracts
New NativeApi methods on the server WebSocket:
todo.load— returns full{ categories: TodoCategory[], items: TodoItem[] }todo.mutate— accepts a batched mutation object and returns the resulting state. Batched to allow a drag operation (reorder + recategorize) to commit atomically.Push events on channel
todos.changedfor live sync if multiple windows are open.Persistence Strategy
Single JSON file at
~/.t3code/todos.json. Read on startup, written via debounced atomic write (write to temp file, rename over target). Archive file at~/.t3code/todos-archive.jsonfor items older than the 10 most recent done items. Archive UI is out of scope for v1.Sidebar Implementation
Matches the diff sidebar pattern exactly: SidebarProvider scoped to the todo panel, Sidebar with
side="right",collapsible="offcanvas",resizableprop, controlled by local React state. Keybinding registered via the existing command system (pattern from diff sidebar toggle).Testing Decisions
Tests should assert external behavior, not implementation details. Focus on state transitions and persistence correctness.
todoStore— Unit tests with plain objects. Test: status cycling, reorder within category, recategorize across categories, archive cutoff logic (item count > 10 in done), category delete guard, JIRA inheritance logic.todoPersistence— Integration test. Test: round-trip read/write, atomic write (no corruption on crash), archive spill on 11th done item.UI components (TodoSidebar, TodoCategory, TodoItem, TodoDetail) are tested implicitly via the
useTodoshook or left for future browser-based tests.Prior art:
packages/sharedalready contains unit-testable modules consumed by both server and web.Out of Scope
cmd+LtoggleFurther Notes
@dnd-kit) and markdown rendering (react-markdown+remark-gfm) are already dependencies inapps/web.doneitems still counts as empty for the toggle.color-mixto derive from the category color).