feat: 'Start agents (n)'-knop in Solo header, productname weg

SoloBoard-header toont nu een primary button die het aantal queueable
TO_DO-taken telt (TO_DO zonder actieve ClaudeJob via
claudeJobsByTaskId-store) en bij klik de nieuwe
enqueueAllTodoJobsAction aanroept. Toast geeft het aantal gestarte
agents terug.

- productname-h1 verwijderd (staat al in NavBar-dropdown, dubbel)
- sprintdoel blijft naast de knop
- 'Toon openstaande stories'-link blijft rechts
- demo-modus disabled met DemoTooltip
- batch-pending state voorkomt dubbele klikken
- productName-prop weg uit SoloBoard + page.tsx (was alleen voor h1)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Janpeter Visser 2026-05-01 10:55:08 +02:00
parent 7d76c3baee
commit 9cd6281816
2 changed files with 36 additions and 6 deletions

View file

@ -105,7 +105,6 @@ export default async function SoloProductPage({ params }: Props) {
return (
<SoloBoard
productId={id}
productName={product.name}
sprintGoal={sprint.sprint_goal}
tasks={tasks}
unassignedStories={unassignedStories}

View file

@ -8,6 +8,9 @@ import {
import { toast } from 'sonner'
import { useSoloStore } from '@/stores/solo-store'
import { taskStatusToApi } from '@/lib/task-status'
import { enqueueAllTodoJobsAction } from '@/actions/claude-jobs'
import { Button } from '@/components/ui/button'
import { DemoTooltip } from '@/components/shared/demo-tooltip'
import { SplitPane } from '@/components/split-pane/split-pane'
import { SoloColumn, type ColumnStatus } from './solo-column'
import { SoloTaskCardOverlay } from './solo-task-card'
@ -30,7 +33,6 @@ export interface SoloTask {
export interface SoloBoardProps {
productId: string
productName: string
sprintGoal: string
tasks: SoloTask[]
unassignedStories: UnassignedStory[]
@ -46,14 +48,16 @@ function getColumnStatus(status: SoloTask['status']): ColumnStatus {
}
export function SoloBoard({
productId, productName, sprintGoal, tasks: initialTasks, unassignedStories: initialUnassigned, isDemo,
productId, sprintGoal, tasks: initialTasks, unassignedStories: initialUnassigned, isDemo,
}: SoloBoardProps) {
const { tasks, initTasks, optimisticMove, rollback, markPending, clearPending } = useSoloStore()
const claudeJobsByTaskId = useSoloStore((s) => s.claudeJobsByTaskId)
const [activeDragId, setActiveDragId] = useState<string | null>(null)
const [selectedTask, setSelectedTask] = useState<SoloTask | null>(null)
const [sheetOpen, setSheetOpen] = useState(false)
const [unassignedStories, setUnassignedStories] = useState(initialUnassigned)
const [, startTransition] = useTransition()
const [batchPending, startBatchTransition] = useTransition()
const taskKey = initialTasks.map(t => t.id).join(',')
useEffect(() => {
@ -125,13 +129,40 @@ export function SoloBoard({
const activeTask = activeDragId ? tasks[activeDragId] : null
const queueableCount = columnTasks.TO_DO.filter(t => {
const job = claudeJobsByTaskId[t.id]
return !job || (job.status !== 'queued' && job.status !== 'claimed' && job.status !== 'running')
}).length
function handleStartAll() {
if (queueableCount === 0) return
startBatchTransition(async () => {
const result = await enqueueAllTodoJobsAction(productId)
if ('error' in result) {
toast.error(result.error)
} else if (result.count === 0) {
toast.info('Geen taken om te starten')
} else {
toast.success(`${result.count} ${result.count === 1 ? 'agent' : 'agents'} ingeschakeld`)
}
})
}
return (
<div className="flex flex-col h-full p-4 gap-4 min-h-0">
<div className="flex items-start justify-between gap-4 shrink-0">
<div className="min-w-0">
<h1 className="text-base font-semibold text-foreground truncate">{productName}</h1>
<div className="min-w-0 flex items-center gap-3">
<DemoTooltip show={isDemo}>
<Button
size="sm"
onClick={handleStartAll}
disabled={isDemo || batchPending || queueableCount === 0}
>
{batchPending ? 'Starten…' : `Start agents (${queueableCount})`}
</Button>
</DemoTooltip>
{sprintGoal && (
<p className="text-sm text-muted-foreground mt-0.5 line-clamp-2">{sprintGoal}</p>
<p className="text-sm text-muted-foreground line-clamp-2 min-w-0">{sprintGoal}</p>
)}
</div>
<button