From db96f53bc25d8f4b353937bf7204e80f4cf74663 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Padez?= Date: Tue, 25 Aug 2026 04:34:41 +0000 Subject: [PATCH] todos: a backburner column, and the parked items on it backburner joins TODO_STATUSES and the board's columns; the three items in the backburner theme move onto the status, so parked is now a lane rather than a folder you have to remember to open. Co-Authored-By: Claude Fable 5 --- TODO.md | 66 +++--- src/servers/api/todos/todo-tree.ts | 2 +- .../src/apps/TodoBoard/TodoBoardPanel.tsx | 215 ++++++++++++++++++ todo/README.md | 5 +- ...our-foreign-keys-are-over-postgres-s-63.md | 2 +- todo/backburner/music-tagging-feature.md | 2 +- todo/backburner/resurrect-projects-apps.md | 2 +- 7 files changed, 255 insertions(+), 39 deletions(-) create mode 100644 src/workspaces/officerdev/src/apps/TodoBoard/TodoBoardPanel.tsx diff --git a/TODO.md b/TODO.md index 0e92e143..599f31b1 100644 --- a/TODO.md +++ b/TODO.md @@ -22,39 +22,39 @@ the owner's OS user and can never be granted. Indirection there really is accide **90 open, 29 closed**, across 31 themes. -| Theme | Open | Closed | -| --- | --- | --- | -| [Affordances](todo/affordances/) | 1 | 0 | -| [Agents coordinating agents](todo/agent-coordination/) | 2 | 0 | -| [Speaking into a session from the backend](todo/agent-delivery/) | 1 | 1 | -| [Backburner](todo/backburner/) | 3 | 0 | -| [Background jobs](todo/background-jobs/) | 2 | 1 | -| [Chat interface](todo/chat/) | 1 | 0 | -| [Code editor](todo/code-editor/) | 1 | 0 | -| [Components — the shared UI layer](todo/components/) | 1 | 0 | -| [File watchers](todo/file-watchers/) | 1 | 0 | -| [Fleet — one command across every server](todo/fleet/) | 5 | 2 | -| [Headscale](todo/headscale/) | 0 | 5 | -| [Infra (alpha)](todo/infra/) | 7 | 0 | -| [Known bugs](todo/known-bugs/) | 3 | 2 | -| [Mobile apps](todo/mobile-apps/) | 3 | 0 | -| [Multi-user](todo/multi-user/) | 16 | 5 | -| [Notifications](todo/notifications/) | 1 | 0 | -| [Plugin ecosystem — before it opens to third parties (2026-08-18)](todo/plugin-ecosystem/) | 3 | 0 | -| [Plugin infrastructure](todo/plugin-infrastructure/) | 2 | 0 | -| [Plugin repos — what writing their READMEs turned up](todo/plugin-repos/) | 10 | 9 | -| [Plugin system — gaps the email extraction found (2026-08-18)](todo/plugin-system/) | 3 | 0 | -| [Prompt library](todo/prompt-library/) | 1 | 0 | -| [SendOff](todo/sendoff/) | 1 | 0 | -| [Settings — portable, and readable by an agent](todo/settings/) | 2 | 0 | -| [Shell — spotlight and the dock](todo/shell/) | 4 | 1 | -| [Sub-agents — agents spawning agents](todo/sub-agents/) | 0 | 2 | -| [Sudo terminal modal](todo/sudo-terminal-modal/) | 1 | 0 | -| [Tooling — the list about the list](todo/tooling/) | 0 | 1 | -| [Transmission](todo/transmission/) | 6 | 0 | -| [URL state — routing, query strings and selection](todo/url-state/) | 4 | 0 | -| [Waiting on the owner to run it](todo/waiting-on-owner/) | 3 | 0 | -| [Workspaces & Panels](todo/workspaces-panels/) | 2 | 0 | +| Theme | Open | Closed | +| ------------------------------------------------------------------------------------------ | ---- | ------ | +| [Affordances](todo/affordances/) | 1 | 0 | +| [Agents coordinating agents](todo/agent-coordination/) | 2 | 0 | +| [Speaking into a session from the backend](todo/agent-delivery/) | 1 | 1 | +| [Backburner](todo/backburner/) | 3 | 0 | +| [Background jobs](todo/background-jobs/) | 2 | 1 | +| [Chat interface](todo/chat/) | 1 | 0 | +| [Code editor](todo/code-editor/) | 1 | 0 | +| [Components — the shared UI layer](todo/components/) | 1 | 0 | +| [File watchers](todo/file-watchers/) | 1 | 0 | +| [Fleet — one command across every server](todo/fleet/) | 5 | 2 | +| [Headscale](todo/headscale/) | 0 | 5 | +| [Infra (alpha)](todo/infra/) | 7 | 0 | +| [Known bugs](todo/known-bugs/) | 3 | 2 | +| [Mobile apps](todo/mobile-apps/) | 3 | 0 | +| [Multi-user](todo/multi-user/) | 16 | 5 | +| [Notifications](todo/notifications/) | 1 | 0 | +| [Plugin ecosystem — before it opens to third parties (2026-08-18)](todo/plugin-ecosystem/) | 3 | 0 | +| [Plugin infrastructure](todo/plugin-infrastructure/) | 2 | 0 | +| [Plugin repos — what writing their READMEs turned up](todo/plugin-repos/) | 10 | 9 | +| [Plugin system — gaps the email extraction found (2026-08-18)](todo/plugin-system/) | 3 | 0 | +| [Prompt library](todo/prompt-library/) | 1 | 0 | +| [SendOff](todo/sendoff/) | 1 | 0 | +| [Settings — portable, and readable by an agent](todo/settings/) | 2 | 0 | +| [Shell — spotlight and the dock](todo/shell/) | 4 | 1 | +| [Sub-agents — agents spawning agents](todo/sub-agents/) | 0 | 2 | +| [Sudo terminal modal](todo/sudo-terminal-modal/) | 1 | 0 | +| [Tooling — the list about the list](todo/tooling/) | 0 | 1 | +| [Transmission](todo/transmission/) | 6 | 0 | +| [URL state — routing, query strings and selection](todo/url-state/) | 4 | 0 | +| [Waiting on the owner to run it](todo/waiting-on-owner/) | 3 | 0 | +| [Workspaces & Panels](todo/workspaces-panels/) | 2 | 0 | ## Records — no items, kept for the reasoning diff --git a/src/servers/api/todos/todo-tree.ts b/src/servers/api/todos/todo-tree.ts index 120b777a..1cd1ce6e 100644 --- a/src/servers/api/todos/todo-tree.ts +++ b/src/servers/api/todos/todo-tree.ts @@ -16,7 +16,7 @@ import { join, resolve } from 'node:path'; /** Resolved per call, not at module load: the server asserts its cwd is the repo root at boot. */ const todoDir = () => resolve(process.cwd(), 'todo'); -export const TODO_STATUSES = ['open', 'doing', 'blocked', 'done'] as const; +export const TODO_STATUSES = ['open', 'doing', 'blocked', 'backburner', 'done'] as const; export type TodoStatus = (typeof TODO_STATUSES)[number]; export type TodoItem = { diff --git a/src/workspaces/officerdev/src/apps/TodoBoard/TodoBoardPanel.tsx b/src/workspaces/officerdev/src/apps/TodoBoard/TodoBoardPanel.tsx new file mode 100644 index 00000000..055a0f41 --- /dev/null +++ b/src/workspaces/officerdev/src/apps/TodoBoard/TodoBoardPanel.tsx @@ -0,0 +1,215 @@ +import type { DragEvent } from 'react'; +import { useState } from 'react'; +import { Link, useSearchParams } from 'react-router'; +import ReactMarkdown from 'react-markdown'; +import remarkGfm from 'remark-gfm'; +import { AlertTriangle, CalendarClock, X } from 'lucide-react'; +import { cn } from '@/lib/utils'; +import type { TodoItem } from './useTodos'; +import { useTodoItem, useTodoPatch, useTodoTree } from './useTodos'; + +/** + * The board over `todo/` — the platform's own backlog, one file per item (todo/README.md). + * + * Selection and the theme filter live in the URL (`?selected=`, `?theme=`), per the navigation audit: + * a card is a real link, a filtered board is an addressable page, and back means what it should. + * + * Writes go through PATCH /todos/item, which touches frontmatter only and regenerates TODO.md — this + * board never holds state the files do not; a drag is a file edit and a refetch. + */ + +const COLUMNS = [ + { status: 'open', label: 'Open' }, + { status: 'doing', label: 'Doing' }, + { status: 'blocked', label: 'Blocked' }, + { status: 'backburner', label: 'Backburner' }, + { status: 'done', label: 'Done' }, +] as const; + +export const TodoBoardPanel = () => { + const { data, isLoading, error } = useTodoTree(); + const patch = useTodoPatch(); + const [params] = useSearchParams(); + const selected = params.get('selected'); + const themeFilter = params.get('theme'); + const [dragOver, setDragOver] = useState(null); + + const href = (changes: Record): string => { + const next = new URLSearchParams(params); + for (const [key, value] of Object.entries(changes)) { + if (value === null) next.delete(key); + else next.set(key, value); + } + const qs = next.toString(); + return qs ? `?${qs}` : '.'; + }; + + if (error) { + return ( +
+ The todo board is owner-only, and this account was refused. Nothing here is broken — there is simply nothing to + show. +
+ ); + } + if (isLoading || !data) { + return
Reading todo/…
; + } + + const themes = data.themes.filter((t) => t.items.length > 0); + const items = themes.flatMap((t) => t.items); + const visible = themeFilter ? items.filter((i) => i.theme === themeFilter) : items; + + const drop = (status: string) => (ev: DragEvent) => { + ev.preventDefault(); + setDragOver(null); + const id = ev.dataTransfer.getData('text/todo-id'); + if (id) patch.mutate({ id, status }); + }; + + return ( +
+
+
+ + All · {items.filter((i) => i.status !== 'done').length} + + {themes.map((t) => { + const open = t.items.filter((i) => i.status !== 'done').length; + return ( + + {t.heading.split('—')[0]?.trim()} · {open} + + ); + })} +
+ +
+ {COLUMNS.map((col) => { + const cards = visible + .filter((i) => i.status === col.status) + .sort((a, b) => (b.priority === 'urgent' ? 1 : 0) - (a.priority === 'urgent' ? 1 : 0)); + return ( +
{ + ev.preventDefault(); + setDragOver(col.status); + }} + onDragLeave={() => setDragOver(null)} + onDrop={drop(col.status)} + className={cn( + 'flex w-64 shrink-0 flex-col rounded-lg border border-border bg-muted/30', + dragOver === col.status && 'border-primary bg-primary/5', + )} + > +
+ {col.label} + {cards.length} +
+
+ {cards.map((item) => ( + + ))} +
+
+ ); + })} +
+
+ + {selected ? : null} +
+ ); +}; + +type TodoCardProps = { item: TodoItem; href: string; selected: boolean }; + +const TodoCard = ({ item, href, selected }: TodoCardProps) => ( + ev.dataTransfer.setData('text/todo-id', item.id)} + className={cn( + 'block rounded-md border border-border bg-card p-2.5 text-left shadow-sm hover:border-primary/50', + selected && 'border-primary', + )} + > +
{item.title}
+
+ {item.theme} + {item.priority === 'urgent' && ( + + urgent + + )} + {item.due && ( + + {item.due} + + )} + {item.created} +
+ +); + +type TodoDetailProps = { id: string; closeHref: string }; + +const TodoDetail = ({ id, closeHref }: TodoDetailProps) => { + const { data, isLoading } = useTodoItem(id); + const patch = useTodoPatch(); + const status = data?.meta.status ?? 'open'; + + return ( +
+
+ {id} +
+ + + + +
+
+
+ {isLoading || !data ? ( + Reading… + ) : ( + {data.body} + )} +
+
+ ); +}; diff --git a/todo/README.md b/todo/README.md index 19cbf853..f660327d 100644 --- a/todo/README.md +++ b/todo/README.md @@ -38,8 +38,9 @@ process everywhere, report which host is behind… - **`id`** — `/`, and it is the filename. Stable: rename the title freely, leave the id alone. Anything pointing at an item points at this. -- **`status`** — `open` or `done` today. The board will want more (`doing`, `blocked`); adding one is a - new value here, not a new file or a new directory. +- **`status`** — `open`, `doing`, `blocked`, `backburner` or `done`. The board (`/todos`) renders one + column per value; adding one is a new entry in `TODO_STATUSES` (`api/todos/todo-tree.ts`), not a new + file or directory. `backburner` is parked-not-dead — still counted as open by the index. - **`created`** — the date the item was raised. **`date_source`** says how much to trust it: `stated` (the prose named a date — true), `section` (inherited from its old section heading — close), `assumed` (unknown, floored at 2026-08-20, the day before the split — a bounded "before today" diff --git a/todo/backburner/four-foreign-keys-are-over-postgres-s-63.md b/todo/backburner/four-foreign-keys-are-over-postgres-s-63.md index f71b279d..8a0ad7f0 100644 --- a/todo/backburner/four-foreign-keys-are-over-postgres-s-63.md +++ b/todo/backburner/four-foreign-keys-are-over-postgres-s-63.md @@ -2,7 +2,7 @@ id: backburner/four-foreign-keys-are-over-postgres-s-63 title: "Four foreign keys are over Postgres's 63-character limit and re-create on every `db:push`" theme: backburner -status: open +status: backburner created: 2026-08-19 date_source: stated order: 1 diff --git a/todo/backburner/music-tagging-feature.md b/todo/backburner/music-tagging-feature.md index 148dc8f5..67c2f75b 100644 --- a/todo/backburner/music-tagging-feature.md +++ b/todo/backburner/music-tagging-feature.md @@ -2,7 +2,7 @@ id: backburner/music-tagging-feature title: "Music tagging feature" theme: backburner -status: open +status: backburner created: 2026-08-20 date_source: assumed order: 2 diff --git a/todo/backburner/resurrect-projects-apps.md b/todo/backburner/resurrect-projects-apps.md index feaaea5c..e65700d4 100644 --- a/todo/backburner/resurrect-projects-apps.md +++ b/todo/backburner/resurrect-projects-apps.md @@ -2,7 +2,7 @@ id: backburner/resurrect-projects-apps title: "Resurrect Projects/Apps" theme: backburner -status: open +status: backburner created: 2026-08-25 date_source: stated order: 3