From 33f554d83cc6a600ec87fe70449b66d40d0b7852 Mon Sep 17 00:00:00 2001 From: Teknium <127238744+teknium1@users.noreply.github.com> Date: Mon, 4 May 2026 03:40:39 -0700 Subject: [PATCH] feat(kanban-dashboard): workspace kind + path inputs in inline create form (#19679) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Closes #18718. Exposes the existing `workspace_kind` + `workspace_path` fields (already accepted by POST /api/plugins/kanban/tasks) in the dashboard's per-column inline-create form so users can create tasks targeting a git worktree or an explicit directory without dropping back to the CLI. - Add a workspace-kind Select (scratch / worktree / dir) to InlineCreate in plugins/kanban/dashboard/dist/index.js. - Conditionally render a workspace_path Input next to the select when kind != scratch; placeholder tells the user whether the path is required (dir) or optional (worktree — derived from assignee when blank). - Submit wires `workspace_kind` / `workspace_path` into the POST body only when they're non-default, keeping the request shape small and interoperable with older dispatcher versions. E2E verified in a dashboard pointed at the worktree: selecting dir + typing /tmp/test-18718 produces a POST body with {workspace_kind: 'dir', workspace_path: '/tmp/test-18718'} and the task lands in sqlite with those fields set. 42/42 kanban dashboard plugin tests pass. --- plugins/kanban/dashboard/dist/index.js | 37 ++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/plugins/kanban/dashboard/dist/index.js b/plugins/kanban/dashboard/dist/index.js index 1b37ef72d4..a818514e2b 100644 --- a/plugins/kanban/dashboard/dist/index.js +++ b/plugins/kanban/dashboard/dist/index.js @@ -919,6 +919,12 @@ const [priority, setPriority] = useState(0); const [parent, setParent] = useState(""); const [skills, setSkills] = useState(""); + // Workspace controls. `scratch` (default) ignores path; `worktree` optionally + // takes a path (dispatcher derives one from the assignee profile otherwise); + // `dir` requires a path. Backend enforces the rule — we only hide/show the + // input here to save vertical space in the common `scratch` case. + const [workspaceKind, setWorkspaceKind] = useState("scratch"); + const [workspacePath, setWorkspacePath] = useState(""); const submit = function () { const trimmed = title.trim(); @@ -938,10 +944,23 @@ .map(function (s) { return s.trim(); }) .filter(function (s) { return s.length > 0; }); if (skillList.length > 0) body.skills = skillList; + // Only send workspace_kind when it's non-default. Keeps the request + // shape small and interoperable with older dispatcher versions. + if (workspaceKind && workspaceKind !== "scratch") { + body.workspace_kind = workspaceKind; + } + const wpTrim = workspacePath.trim(); + if (wpTrim) body.workspace_path = wpTrim; props.onSubmit(body); setTitle(""); setAssignee(""); setPriority(0); setParent(""); setSkills(""); + setWorkspaceKind("scratch"); setWorkspacePath(""); }; + const showPathInput = workspaceKind !== "scratch"; + const pathPlaceholder = workspaceKind === "dir" + ? "workspace path (required, e.g. ~/projects/my-app)" + : "workspace path (optional, derived from assignee if blank)"; + return h("div", { className: "hermes-kanban-inline-create" }, h(Input, { value: title, @@ -978,6 +997,24 @@ title: "Force-load these skills into the worker (in addition to the built-in kanban-worker).", className: "h-7 text-xs", }), + h("div", { className: "flex gap-2" }, + h(Select, { + value: workspaceKind, + onChange: function (e) { setWorkspaceKind(e.target.value); }, + title: "scratch: isolated temp dir (default). worktree: git worktree on the assignee profile. dir: exact path (required below).", + className: "h-7 text-xs w-28", + }, + h(SelectOption, { value: "scratch" }, "scratch"), + h(SelectOption, { value: "worktree" }, "worktree"), + h(SelectOption, { value: "dir" }, "dir"), + ), + showPathInput ? h(Input, { + value: workspacePath, + onChange: function (e) { setWorkspacePath(e.target.value); }, + placeholder: pathPlaceholder, + className: "h-7 text-xs flex-1", + }) : null, + ), h(Select, { value: parent, onChange: function (e) { setParent(e.target.value); },