Skip to content

Commit 18a30fd

Browse files
authored
feat: redesign task board UI (#24)
* v0.7.1: server extraction, workspaces, kanban redesign, and landing page - feat: extract standalone server for multi-client architecture (#18) - feat: add workspaces to group projects (#17) - feat: redesign kanban board cards and columns (#16) - feat: add landing page with OS-detected download buttons (#14, #19) - fix: mini-card close cleanup and confirm popover placement (#15) * feat: redesign task board UI with polished kanban, list, and inline task creation - Kanban cards: task short IDs, status icons, date footer, top-right menu on hover - List view: collapsible colored section bars with chevron toggle and animations - Pill-style segmented view toggle for List/Board and Grid/Tabs - Inline floating form for task creation replacing centered modal - Add task buttons in kanban columns and list sections * fix: address PR review comments - Fix nested button in list view section headers (use div + proper button) - Plumb status through to AddTaskDialog so column/section + buttons create tasks with the correct initial status - Fix useEffect deps for dialog init (include editingTask) - Use stable task ID suffix for short IDs instead of order field
1 parent 12dcc67 commit 18a30fd

14 files changed

Lines changed: 553 additions & 384 deletions

.yarn/install-state.gz

463 Bytes
Binary file not shown.

src/renderer/App.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ import { OnboardingModal } from './components/OnboardingModal'
3131
import { TerminalPanel } from './components/TerminalPanel'
3232
import { UpdateBanner } from './components/UpdateBanner'
3333
import { ToastContainer } from './components/Toast'
34+
import { AddTaskDialog } from './components/AddTaskDialog'
3435

3536
const isMac = navigator.platform.toUpperCase().includes('MAC')
3637

@@ -365,7 +366,7 @@ export function App() {
365366
</svg>
366367
</button>
367368
<button
368-
onClick={() => useAppStore.getState().setSelectedTaskId('new')}
369+
onClick={() => useAppStore.getState().setTaskDialogOpen(true)}
369370
className="px-3 py-1.5 text-sm font-medium text-gray-200
370371
hover:text-white bg-white/[0.06] hover:bg-white/[0.1]
371372
rounded-md transition-colors flex items-center gap-2"
@@ -403,6 +404,7 @@ export function App() {
403404
<AddProjectDialog />
404405
<WorkflowEditor />
405406
<CommandPalette />
407+
<AddTaskDialog />
406408
<WorktreeCleanupDialog />
407409
<MissedScheduleDialog />
408410
<DiffSidebar />

src/renderer/components/AddTaskDialog.tsx

Lines changed: 146 additions & 143 deletions
Large diffs are not rendered by default.

src/renderer/components/GridToolbar.tsx

Lines changed: 28 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -184,12 +184,11 @@ export function GridToolbar() {
184184
const setConfig = useAppStore((s) => s.setConfig)
185185
const layoutMode = config?.defaults?.layoutMode ?? 'grid'
186186

187-
const toggleLayout = (): void => {
188-
if (!config) return
189-
const next = layoutMode === 'grid' ? 'tabs' : 'grid'
187+
const setLayoutMode = (mode: 'grid' | 'tabs'): void => {
188+
if (!config || layoutMode === mode) return
190189
const updated = {
191190
...config,
192-
defaults: { ...config.defaults, layoutMode: next as 'grid' | 'tabs' }
191+
defaults: { ...config.defaults, layoutMode: mode }
193192
}
194193
window.api.saveConfig(updated)
195194
setConfig(updated)
@@ -229,15 +228,31 @@ export function GridToolbar() {
229228
/>
230229
)}
231230

232-
{/* Layout toggle */}
233-
<button
234-
onClick={toggleLayout}
235-
className="flex items-center gap-1.5 px-2 py-1 rounded-md text-xs transition-colors
236-
text-gray-400 hover:text-white hover:bg-white/[0.06]"
237-
title={layoutMode === 'grid' ? 'Switch to tabs' : 'Switch to grid'}
238-
>
239-
{layoutMode === 'grid' ? TabIcon : GridIcon}
240-
</button>
231+
{/* Layout toggle — pill segmented control */}
232+
<div className="flex items-center bg-white/[0.06] rounded-lg p-0.5">
233+
<button
234+
onClick={() => setLayoutMode('grid')}
235+
className={`flex items-center gap-1.5 px-2.5 py-1 rounded-md text-xs transition-colors ${
236+
layoutMode === 'grid'
237+
? 'bg-white/[0.1] text-white'
238+
: 'text-gray-500 hover:text-gray-300'
239+
}`}
240+
>
241+
{GridIcon}
242+
<span>Grid</span>
243+
</button>
244+
<button
245+
onClick={() => setLayoutMode('tabs')}
246+
className={`flex items-center gap-1.5 px-2.5 py-1 rounded-md text-xs transition-colors ${
247+
layoutMode === 'tabs'
248+
? 'bg-white/[0.1] text-white'
249+
: 'text-gray-500 hover:text-gray-300'
250+
}`}
251+
>
252+
{TabIcon}
253+
<span>Tabs</span>
254+
</button>
255+
</div>
241256
</div>
242257
)
243258
}

src/renderer/components/ProjectSidebar.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -322,6 +322,7 @@ export function ProjectSidebar() {
322322
const setFocusedTerminal = useAppStore((s) => s.setFocusedTerminal)
323323
const setMainViewMode = useAppStore((s) => s.setMainViewMode)
324324
const setSelectedTaskId = useAppStore((s) => s.setSelectedTaskId)
325+
const setTaskDialogOpen = useAppStore((s) => s.setTaskDialogOpen)
325326
const archivedSessions = useAppStore((s) => s.archivedSessions)
326327
const showArchivedSessions = useAppStore((s) => s.showArchivedSessions)
327328
const setShowArchivedSessions = useAppStore((s) => s.setShowArchivedSessions)
@@ -678,7 +679,7 @@ export function ProjectSidebar() {
678679
e.stopPropagation()
679680
setActiveProject(project.name)
680681
setMainViewMode('tasks')
681-
setSelectedTaskId('new')
682+
setTaskDialogOpen(true)
682683
}}
683684
className="opacity-0 group-hover:opacity-100 text-gray-600 hover:text-blue-400
684685
p-1 rounded-md hover:bg-white/[0.06] transition-all"
@@ -1017,7 +1018,7 @@ export function ProjectSidebar() {
10171018
onClick={() => {
10181019
setActiveProject(project.name)
10191020
setMainViewMode('tasks')
1020-
setSelectedTaskId('new')
1021+
setTaskDialogOpen(true)
10211022
}}
10221023
className="text-gray-600 hover:text-gray-300 p-0.5 transition-colors"
10231024
>

src/renderer/components/TaskBoardView.tsx

Lines changed: 30 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ export function TaskBoardView() {
1919
const terminals = useAppStore((s) => s.terminals)
2020
const setFocusedTerminal = useAppStore((s) => s.setFocusedTerminal)
2121
const setSelectedTaskId = useAppStore((s) => s.setSelectedTaskId)
22+
const setTaskDialogOpen = useAppStore((s) => s.setTaskDialogOpen)
2223
const taskStatusFilter = useAppStore((s) => s.taskStatusFilter)
2324

2425
const viewMode = config?.defaults?.taskViewMode ?? 'list'
@@ -117,13 +118,33 @@ export function TaskBoardView() {
117118
setSelectedTaskId(task.id)
118119
}
119120

120-
const sections = [
121-
{ title: 'Todo', tasks: todoTasks, emptyText: 'No tasks in queue' },
122-
{ title: 'In Progress', tasks: inProgressTasks, emptyText: 'No active tasks' },
123-
{ title: 'In Review', tasks: inReviewTasks, emptyText: 'No tasks awaiting review' },
124-
{ title: 'Done', tasks: doneTasks, emptyText: 'No completed tasks' },
125-
{ title: 'Cancelled', tasks: cancelledTasks, emptyText: 'No cancelled tasks' }
126-
]
121+
const handleAddTask = (status: TaskStatus) => {
122+
setTaskDialogOpen(true, status)
123+
}
124+
125+
const sections: { status: TaskStatus; title: string; tasks: TaskConfig[]; emptyText: string }[] =
126+
[
127+
{ status: 'todo', title: 'Todo', tasks: todoTasks, emptyText: 'No tasks in queue' },
128+
{
129+
status: 'in_progress',
130+
title: 'In Progress',
131+
tasks: inProgressTasks,
132+
emptyText: 'No active tasks'
133+
},
134+
{
135+
status: 'in_review',
136+
title: 'In Review',
137+
tasks: inReviewTasks,
138+
emptyText: 'No tasks awaiting review'
139+
},
140+
{ status: 'done', title: 'Done', tasks: doneTasks, emptyText: 'No completed tasks' },
141+
{
142+
status: 'cancelled',
143+
title: 'Cancelled',
144+
tasks: cancelledTasks,
145+
emptyText: 'No cancelled tasks'
146+
}
147+
]
127148

128149
const totalTasks = allTasks.length
129150

@@ -170,6 +191,7 @@ export function TaskBoardView() {
170191
}}
171192
onReviewDiff={(id) => setSelectedTaskId(id)}
172193
onSelect={handleSelect}
194+
onAddTask={handleAddTask}
173195
isSessionLive={isSessionLive}
174196
/>
175197
) : (
@@ -196,6 +218,7 @@ export function TaskBoardView() {
196218
}}
197219
onReviewDiff={(id) => setSelectedTaskId(id)}
198220
onSelect={handleSelect}
221+
onAddTask={handleAddTask}
199222
isSessionLive={isSessionLive}
200223
/>
201224
)}

src/renderer/components/TaskToolbar.tsx

Lines changed: 28 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -164,12 +164,11 @@ export function TaskToolbar() {
164164
const setConfig = useAppStore((s) => s.setConfig)
165165
const taskViewMode = (config?.defaults?.taskViewMode ?? 'list') as TaskViewMode
166166

167-
const toggleViewMode = (): void => {
168-
if (!config) return
169-
const next = taskViewMode === 'list' ? 'kanban' : 'list'
167+
const setViewMode = (mode: TaskViewMode): void => {
168+
if (!config || taskViewMode === mode) return
170169
const updated = {
171170
...config,
172-
defaults: { ...config.defaults, taskViewMode: next as TaskViewMode }
171+
defaults: { ...config.defaults, taskViewMode: mode }
173172
}
174173
window.api.saveConfig(updated)
175174
setConfig(updated)
@@ -187,15 +186,31 @@ export function TaskToolbar() {
187186
label="Status"
188187
/>
189188

190-
{/* View toggle */}
191-
<button
192-
onClick={toggleViewMode}
193-
className="flex items-center gap-1.5 px-2 py-1 rounded-md text-xs transition-colors
194-
text-gray-400 hover:text-white hover:bg-white/[0.06]"
195-
title={taskViewMode === 'list' ? 'Switch to kanban' : 'Switch to list'}
196-
>
197-
{taskViewMode === 'list' ? KanbanIcon : ListIcon}
198-
</button>
189+
{/* View toggle — pill segmented control */}
190+
<div className="flex items-center bg-white/[0.06] rounded-lg p-0.5">
191+
<button
192+
onClick={() => setViewMode('list')}
193+
className={`flex items-center gap-1.5 px-2.5 py-1 rounded-md text-xs transition-colors ${
194+
taskViewMode === 'list'
195+
? 'bg-white/[0.1] text-white'
196+
: 'text-gray-500 hover:text-gray-300'
197+
}`}
198+
>
199+
{ListIcon}
200+
<span>List</span>
201+
</button>
202+
<button
203+
onClick={() => setViewMode('kanban')}
204+
className={`flex items-center gap-1.5 px-2.5 py-1 rounded-md text-xs transition-colors ${
205+
taskViewMode === 'kanban'
206+
? 'bg-white/[0.1] text-white'
207+
: 'text-gray-500 hover:text-gray-300'
208+
}`}
209+
>
210+
{KanbanIcon}
211+
<span>Board</span>
212+
</button>
213+
</div>
199214
</div>
200215
)
201216
}

0 commit comments

Comments
 (0)