Add concurrency caps, the two-pane web UI, AGENTS.md, and plan status
This commit is contained in:
@@ -0,0 +1,73 @@
|
||||
import { post, type Project } from "./api.js";
|
||||
import { errorText, h, replace } from "./dom.js";
|
||||
import { currentSession, onEach, state } from "./state.js";
|
||||
|
||||
export function mountTasks(root: HTMLElement): () => void {
|
||||
const render = () => replace(root, h("div", { class: "tasks-view" }, ...state.projects.map(projectBlock), newProjectForm()));
|
||||
const off = onEach(["projects", "tasks", "sessions", "inbox", "selection"], render);
|
||||
render();
|
||||
return off;
|
||||
}
|
||||
|
||||
function projectBlock(project: Project): HTMLElement {
|
||||
const tasks = (state.tasks.get(project.id) ?? []).filter((t) => t.status === "active");
|
||||
|
||||
return h(
|
||||
"section",
|
||||
{ class: "project" },
|
||||
h("h2", {}, project.name, " ", h("span", { class: "repo" }, project.repo_path)),
|
||||
h(
|
||||
"ul",
|
||||
{ class: "task-list" },
|
||||
...tasks.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}` },
|
||||
h("span", { class: `dot ${sess?.state ?? "none"}`, title: sess?.state ?? "no session" }),
|
||||
h("span", { class: "task-title" }, task.title),
|
||||
h("span", { class: "task-state" }, sess?.state ?? "no agent"),
|
||||
count > 0 && h("span", { class: "badge" }, count),
|
||||
),
|
||||
);
|
||||
}),
|
||||
tasks.length === 0 && h("li", { class: "empty-row" }, "No active tasks."),
|
||||
),
|
||||
newTaskForm(project.id),
|
||||
);
|
||||
}
|
||||
|
||||
function newTaskForm(projectId: string): HTMLElement {
|
||||
const title = h("input", { placeholder: "New task title", required: true });
|
||||
const form = h("form", { class: "inline-form" }, title, h("button", { type: "submit" }, "Create"));
|
||||
form.onsubmit = async (e) => {
|
||||
e.preventDefault();
|
||||
try {
|
||||
const task = await post("/api/projects/{projectId}/tasks", { path: { projectId }, body: { title: title.value } });
|
||||
location.hash = `#/task/${task.id}`;
|
||||
} catch (err) {
|
||||
alert(errorText(err));
|
||||
}
|
||||
};
|
||||
return form;
|
||||
}
|
||||
|
||||
function newProjectForm(): HTMLElement {
|
||||
const name = h("input", { placeholder: "Name", required: true });
|
||||
const path = h("input", { placeholder: "Repo path on the server", required: true });
|
||||
const form = h("form", { class: "stack-form" }, name, path, h("button", { type: "submit" }, "Add project"));
|
||||
form.onsubmit = async (e) => {
|
||||
e.preventDefault();
|
||||
try {
|
||||
await post("/api/projects", { body: { name: name.value, repo_path: path.value } });
|
||||
form.reset();
|
||||
} catch (err) {
|
||||
alert(errorText(err));
|
||||
}
|
||||
};
|
||||
return h("details", { class: "add-project", open: state.projects.length === 0 }, h("summary", {}, "Add project"), form);
|
||||
}
|
||||
Reference in New Issue
Block a user