feat(HAI-002): integrate worktree grouping into Column and Board

This commit is contained in:
Dustin Byrne
2026-03-25 18:53:21 -04:00
parent 16458c59b5
commit 3b79792f27
3 changed files with 38 additions and 4 deletions

View File

@@ -1,5 +1,6 @@
import { useState, useCallback } from "react";
import { useState, useCallback, useEffect } from "react";
import type { TaskDetail } from "@hai/core";
import { fetchConfig } from "./api";
import { Header } from "./components/Header";
import { Board } from "./components/Board";
import { CreateTaskModal } from "./components/CreateTaskModal";
@@ -11,7 +12,14 @@ import { ToastProvider, useToast } from "./hooks/useToast";
function AppInner() {
const [createModalOpen, setCreateModalOpen] = useState(false);
const [detailTask, setDetailTask] = useState<TaskDetail | null>(null);
const [maxConcurrent, setMaxConcurrent] = useState(2);
const { tasks, createTask, moveTask, deleteTask, mergeTask } = useTasks();
useEffect(() => {
fetchConfig()
.then((cfg) => setMaxConcurrent(cfg.maxConcurrent))
.catch(() => {/* keep default */});
}, []);
const { toasts, addToast, removeToast } = useToast();
const handleCreateOpen = useCallback(() => setCreateModalOpen(true), []);
@@ -28,6 +36,7 @@ function AppInner() {
<Header onNewTask={handleCreateOpen} />
<Board
tasks={tasks}
maxConcurrent={maxConcurrent}
onMoveTask={moveTask}
onOpenDetail={handleDetailOpen}
addToast={addToast}

View File

@@ -5,12 +5,13 @@ import type { ToastType } from "../hooks/useToast";
interface BoardProps {
tasks: Task[];
maxConcurrent: number;
onMoveTask: (id: string, column: ColumnType) => Promise<Task>;
onOpenDetail: (task: TaskDetail) => void;
addToast: (message: string, type?: ToastType) => void;
}
export function Board({ tasks, onMoveTask, onOpenDetail, addToast }: BoardProps) {
export function Board({ tasks, maxConcurrent, onMoveTask, onOpenDetail, addToast }: BoardProps) {
return (
<main className="board" id="board">
{COLUMNS.map((col) => (
@@ -18,6 +19,8 @@ export function Board({ tasks, onMoveTask, onOpenDetail, addToast }: BoardProps)
key={col}
column={col}
tasks={tasks.filter((t) => t.column === col)}
allTasks={tasks}
maxConcurrent={maxConcurrent}
onMoveTask={onMoveTask}
onOpenDetail={onOpenDetail}
addToast={addToast}

View File

@@ -2,17 +2,21 @@ import { useState, useCallback } from "react";
import type { Task, TaskDetail, Column as ColumnType } from "@hai/core";
import { COLUMN_LABELS, COLUMN_DESCRIPTIONS } from "@hai/core";
import { TaskCard } from "./TaskCard";
import { WorktreeGroup } from "./WorktreeGroup";
import { groupByWorktree } from "../utils/worktreeGrouping";
import type { ToastType } from "../hooks/useToast";
interface ColumnProps {
column: ColumnType;
tasks: Task[];
allTasks: Task[];
maxConcurrent: number;
onMoveTask: (id: string, column: ColumnType) => Promise<Task>;
onOpenDetail: (task: TaskDetail) => void;
addToast: (message: string, type?: ToastType) => void;
}
export function Column({ column, tasks, onMoveTask, onOpenDetail, addToast }: ColumnProps) {
export function Column({ column, tasks, allTasks, maxConcurrent, onMoveTask, onOpenDetail, addToast }: ColumnProps) {
const [dragOver, setDragOver] = useState(false);
const handleDragOver = useCallback((e: React.DragEvent) => {
@@ -56,7 +60,25 @@ export function Column({ column, tasks, onMoveTask, onOpenDetail, addToast }: Co
</div>
<p className="column-desc">{COLUMN_DESCRIPTIONS[column]}</p>
<div className="column-body">
{tasks.length === 0 ? (
{column === "in-progress" ? (
(() => {
const groups = groupByWorktree(tasks, allTasks, maxConcurrent);
return groups.length === 0 ? (
<div className="empty-column">No tasks</div>
) : (
groups.map((group) => (
<WorktreeGroup
key={group.label}
label={group.label}
activeTasks={group.activeTasks}
queuedTasks={group.queuedTasks}
onOpenDetail={onOpenDetail}
addToast={addToast}
/>
))
);
})()
) : tasks.length === 0 ? (
<div className="empty-column">No tasks</div>
) : (
tasks.map((task) => (