Replace the Todo tab with a Project dashboard of tasks and TODO items
This commit is contained in:
+1
-1
@@ -1019,7 +1019,7 @@ export interface components {
|
||||
focused: boolean;
|
||||
};
|
||||
/** @enum {string} */
|
||||
TaskTab: "transcript" | "changes" | "files" | "inbox" | "todo";
|
||||
TaskTab: "transcript" | "changes" | "files" | "inbox" | "project";
|
||||
Status: {
|
||||
/** @description Agent processes currently running. */
|
||||
live_agents: number;
|
||||
|
||||
+2
-2
@@ -106,8 +106,8 @@ function openSwitcher(): void {
|
||||
);
|
||||
return { el, pick: () => go(task.id) };
|
||||
});
|
||||
for (const p of state.projects.filter((p) => `${p.name} todo`.toLowerCase().includes(q))) {
|
||||
const el = h("li", {}, h("span", { class: "dot none" }), h("span", { class: "task-title" }, h("span", { class: "project-name" }, p.name), " TODO.md"), h("span", { class: "task-state" }, "project"));
|
||||
for (const p of state.projects.filter((p) => `${p.name} project`.toLowerCase().includes(q))) {
|
||||
const el = h("li", {}, h("span", { class: "dot none" }), h("span", { class: "task-title" }, h("span", { class: "project-name" }, p.name)), h("span", { class: "task-state" }, "project"));
|
||||
rows.push({
|
||||
el,
|
||||
pick: () => {
|
||||
|
||||
+2
-2
@@ -5,15 +5,15 @@ import { mountFiles } from "./files.js";
|
||||
import { mountInbox } from "./inbox.js";
|
||||
import { mountLead } from "./lead.js";
|
||||
import { focusTab, on, state, tabs } from "./state.js";
|
||||
import { mountProject } from "./project.js";
|
||||
import { mountTranscript } from "./task.js";
|
||||
import { mountTodo } from "./todo.js";
|
||||
|
||||
const views: Record<TaskTab, { label: string; mount: (root: HTMLElement) => () => void }> = {
|
||||
transcript: { label: "Transcript", mount: mountTranscript },
|
||||
changes: { label: "Changes", mount: (root) => mountChanges(root) },
|
||||
files: { label: "Files", mount: mountFiles },
|
||||
inbox: { label: "Inbox", mount: mountInbox },
|
||||
todo: { label: "Todo", mount: mountTodo },
|
||||
project: { label: "Project", mount: mountProject },
|
||||
};
|
||||
|
||||
export function leadPane(): HTMLElement {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { get, post, type ByreEvent, type Project, type Schemas } from "./api.js";
|
||||
import { get, post, type ByreEvent, type Project, type Schemas, type Task } from "./api.js";
|
||||
import { errorText, h, replace, showAlert } from "./dom.js";
|
||||
import { markdown } from "./markdown.js";
|
||||
import { models } from "./task.js";
|
||||
@@ -9,16 +9,16 @@ type TodoItem = Schemas["TodoItem"];
|
||||
|
||||
const refreshKinds = new Set(["task.merged", "project.created"]);
|
||||
|
||||
export function mountTodo(root: HTMLElement): () => void {
|
||||
export function mountProject(root: HTMLElement): () => void {
|
||||
const todos = new Map<string, Todo | string>();
|
||||
const starting = new Set<string>();
|
||||
let disposed = false;
|
||||
|
||||
const render = () => {
|
||||
const scrollTop = root.querySelector(".todo-view")?.scrollTop ?? 0;
|
||||
const scrollTop = root.querySelector(".project-view")?.scrollTop ?? 0;
|
||||
const p = focusedProject();
|
||||
replace(root, h("div", { class: "todo-view" }, p ? projectBlock(p, todos.get(p.id), starting, start) : h("div", { class: "empty" }, "No project selected.")));
|
||||
root.querySelector(".todo-view")!.scrollTop = scrollTop;
|
||||
replace(root, h("div", { class: "project-view" }, p ? projectBlock(p, todos.get(p.id), starting, start) : h("div", { class: "empty" }, "No project selected.")));
|
||||
root.querySelector(".project-view")!.scrollTop = scrollTop;
|
||||
};
|
||||
|
||||
const load = async (projectId: string) => {
|
||||
@@ -79,7 +79,7 @@ export function mountTodo(root: HTMLElement): () => void {
|
||||
void load(p.id);
|
||||
}
|
||||
}),
|
||||
onEach(["tasks", "sessions"], render),
|
||||
onEach(["tasks", "sessions", "inbox"], render),
|
||||
];
|
||||
|
||||
render();
|
||||
@@ -94,22 +94,64 @@ export function mountTodo(root: HTMLElement): () => void {
|
||||
}
|
||||
|
||||
function projectBlock(project: Project, todo: Todo | string | undefined, starting: Set<string>, start: (p: Project, item: TodoItem) => Promise<void>): HTMLElement {
|
||||
const head = h("h2", {}, project.name, " ", h("span", { class: "repo" }, `TODO.md on ${project.default_branch}`));
|
||||
const active = (state.tasks.get(project.id) ?? []).filter((t) => t.status === "active");
|
||||
return h(
|
||||
"section",
|
||||
{ class: "project" },
|
||||
h("h2", {}, project.name, " ", h("span", { class: "repo" }, project.default_branch)),
|
||||
h("h3", {}, "Tasks"),
|
||||
tasksBlock(active),
|
||||
h("h3", {}, "TODO"),
|
||||
...todoBlock(project, todo, active, starting, start),
|
||||
);
|
||||
}
|
||||
|
||||
function tasksBlock(active: Task[]): HTMLElement {
|
||||
if (active.length === 0) {
|
||||
return h("p", { class: "empty-row" }, "No active tasks.");
|
||||
}
|
||||
return h(
|
||||
"ul",
|
||||
{ class: "task-list" },
|
||||
...active.map((task) => {
|
||||
const sess = currentSession(task.id);
|
||||
const count = state.inbox.filter((item) => item.task_id === task.id).length;
|
||||
return h(
|
||||
"li",
|
||||
{ class: task.id === state.selectedTaskId ? "selected" : "" },
|
||||
h(
|
||||
"a",
|
||||
{
|
||||
href: `#/task/${task.id}`,
|
||||
onclick: (e: MouseEvent) => {
|
||||
e.preventDefault();
|
||||
focusTask(task.id);
|
||||
},
|
||||
},
|
||||
h("span", { class: `dot ${sess?.state ?? "none"}`, title: sess?.state ?? "no session" }),
|
||||
h("span", { class: "task-title" }, task.title),
|
||||
count > 0 && h("span", { class: "badge", title: "inbox items" }, count),
|
||||
h("span", { class: "task-state" }, sess?.state ?? "no agent"),
|
||||
),
|
||||
);
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
function todoBlock(project: Project, todo: Todo | string | undefined, active: Task[], starting: Set<string>, start: (p: Project, item: TodoItem) => Promise<void>): HTMLElement[] {
|
||||
if (todo === undefined) {
|
||||
return h("section", { class: "project" }, head, h("p", { class: "empty-row" }, "Loading…"));
|
||||
return [h("p", { class: "empty-row" }, "Loading…")];
|
||||
}
|
||||
if (typeof todo === "string") {
|
||||
return h("section", { class: "project" }, head, h("p", { class: "empty-row error-text" }, todo));
|
||||
return [h("p", { class: "empty-row error-text" }, todo)];
|
||||
}
|
||||
if (!todo.exists) {
|
||||
return h("section", { class: "project" }, head, h("p", { class: "empty-row" }, `No TODO.md on ${project.default_branch}.`));
|
||||
return [h("p", { class: "empty-row" }, `No TODO.md on ${project.default_branch}.`)];
|
||||
}
|
||||
if (todo.items.length === 0) {
|
||||
return h("section", { class: "project" }, head, h("p", { class: "empty-row" }, "TODO.md has no items."));
|
||||
return [h("p", { class: "empty-row" }, "TODO.md has no items.")];
|
||||
}
|
||||
|
||||
const active = (state.tasks.get(project.id) ?? []).filter((t) => t.status === "active" && t.todo_item);
|
||||
const children: HTMLElement[] = [];
|
||||
let section = "";
|
||||
let list: HTMLElement | undefined;
|
||||
@@ -117,7 +159,7 @@ function projectBlock(project: Project, todo: Todo | string | undefined, startin
|
||||
if (!list || item.section !== section) {
|
||||
section = item.section;
|
||||
if (section) {
|
||||
children.push(h("h3", { class: "todo-section" }, section));
|
||||
children.push(h("h4", { class: "todo-section" }, section));
|
||||
}
|
||||
list = h("ul", { class: "todo-list" });
|
||||
children.push(list);
|
||||
@@ -156,6 +198,5 @@ function projectBlock(project: Project, todo: Todo | string | undefined, startin
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return h("section", { class: "project" }, head, ...children);
|
||||
return children;
|
||||
}
|
||||
+2
-2
@@ -17,7 +17,7 @@ export type Topic =
|
||||
| "lead.transcript"
|
||||
| "lead.stream";
|
||||
|
||||
export const tabs: TaskTab[] = ["transcript", "changes", "files", "inbox", "todo"];
|
||||
export const tabs: TaskTab[] = ["transcript", "changes", "files", "inbox", "project"];
|
||||
|
||||
export const state = {
|
||||
me: undefined as User | undefined,
|
||||
@@ -161,7 +161,7 @@ export function focusTask(taskId: string, tab: TaskTab = "transcript"): void {
|
||||
}
|
||||
|
||||
export function focusProject(projectId: string): void {
|
||||
setFocus({ task_id: null, project_id: projectId, tab: "todo" }).catch((err: unknown) => console.error("set focus", err));
|
||||
setFocus({ task_id: null, project_id: projectId, tab: "project" }).catch((err: unknown) => console.error("set focus", err));
|
||||
}
|
||||
|
||||
export function focusTab(tab: TaskTab): void {
|
||||
|
||||
Reference in New Issue
Block a user