feat: GitHub-link op DONE-card + pushed_at doorvoer

- lib/job-status-url.ts: getBranchUrl(repoUrl, branch) → GitHub tree URL
- JobState + ClaudeJobEvent: pushed_at? veld toegevoegd
- realtime/solo/route.ts: pushed_at in Prisma-select, JobPayload en mapping
- SoloBoardProps + TaskDetailDialog: repoUrl prop doorgevoerd
- task-detail-dialog: "Open op GitHub"-link als done + pushed_at + branch + repoUrl
- 3 unit-tests voor getBranchUrl; totaal 261 tests groen

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Janpeter Visser 2026-05-01 12:04:47 +02:00
parent 120a05347b
commit f59f4754df
7 changed files with 55 additions and 9 deletions

View file

@ -38,6 +38,7 @@ export interface SoloBoardProps {
unassignedStories: UnassignedStory[]
isDemo: boolean
currentUserId: string
repoUrl?: string | null
}
const COLUMN_STATUSES: ColumnStatus[] = ['TO_DO', 'IN_PROGRESS', 'DONE']
@ -48,7 +49,7 @@ function getColumnStatus(status: SoloTask['status']): ColumnStatus {
}
export function SoloBoard({
productId, sprintGoal, tasks: initialTasks, unassignedStories: initialUnassigned, isDemo,
productId, sprintGoal, tasks: initialTasks, unassignedStories: initialUnassigned, isDemo, repoUrl,
}: SoloBoardProps) {
const { tasks, initTasks, optimisticMove, rollback, markPending, clearPending } = useSoloStore()
const claudeJobsByTaskId = useSoloStore((s) => s.claudeJobsByTaskId)
@ -219,6 +220,7 @@ export function SoloBoard({
task={selectedTask}
productId={productId}
isDemo={isDemo}
repoUrl={repoUrl}
onClose={() => setSelectedTask(null)}
/>

View file

@ -13,6 +13,7 @@ import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/comp
import { useSoloStore } from '@/stores/solo-store'
import { enqueueClaudeJobAction, cancelClaudeJobAction } from '@/actions/claude-jobs'
import { cn } from '@/lib/utils'
import { getBranchUrl } from '@/lib/job-status-url'
import type { SoloTask } from './solo-board'
const STATUS_COLORS: Record<string, string> = {
@ -33,6 +34,7 @@ interface TaskDetailDialogProps {
task: SoloTask | null
productId: string
isDemo: boolean
repoUrl?: string | null
onClose: () => void
}
@ -40,12 +42,13 @@ interface TaskDetailContentProps {
task: SoloTask
productId: string
isDemo: boolean
repoUrl?: string | null
onClose: () => void
}
type SaveState = 'idle' | 'saving' | 'saved'
function TaskDetailContent({ task, productId, isDemo, onClose }: TaskDetailContentProps) {
function TaskDetailContent({ task, productId, isDemo, repoUrl, onClose }: TaskDetailContentProps) {
const { updatePlan } = useSoloStore()
const job = useSoloStore(s => s.claudeJobsByTaskId[task.id])
const connectedWorkers = useSoloStore(s => s.connectedWorkers)
@ -206,8 +209,18 @@ function TaskDetailContent({ task, productId, isDemo, onClose }: TaskDetailConte
)}
{job?.status === 'done' && (
<span className="text-xs text-status-done">
Klaar{job.branch ? ` — branch ${job.branch}` : ''}
<span className="text-xs text-status-done flex items-center gap-2">
Klaar{job.branch && !job.pushed_at ? ` — branch ${job.branch}` : ''}
{job.pushed_at && job.branch && repoUrl && (
<a
href={getBranchUrl(repoUrl, job.branch)}
target="_blank"
rel="noopener noreferrer"
className="underline text-primary hover:text-primary/80"
>
Open op GitHub
</a>
)}
</span>
)}
@ -219,7 +232,7 @@ function TaskDetailContent({ task, productId, isDemo, onClose }: TaskDetailConte
)
}
export function TaskDetailDialog({ task, productId, isDemo, onClose }: TaskDetailDialogProps) {
export function TaskDetailDialog({ task, productId, isDemo, repoUrl, onClose }: TaskDetailDialogProps) {
return (
<Dialog open={!!task} onOpenChange={(open) => { if (!open) onClose() }}>
<DialogContent className="sm:max-w-lg">
@ -229,6 +242,7 @@ export function TaskDetailDialog({ task, productId, isDemo, onClose }: TaskDetai
task={task}
productId={productId}
isDemo={isDemo}
repoUrl={repoUrl}
onClose={onClose}
/>
)}