From 14041d007fc8c3b06866d044eef6e995927679a7 Mon Sep 17 00:00:00 2001 From: Lukas May Date: Fri, 6 Mar 2026 16:27:44 +0100 Subject: [PATCH] feat: add Errands nav item, /errands route, and CreateErrandDialog - AppLayout: add Errands nav entry with pending_review badge count - /errands route: list table with ID, description, branch, status, agent, created columns; empty state with CLI hint; slide-over integration - CreateErrandDialog: description (max 200 chars with counter), project select, optional base branch; no optimistic UI due to agent spawn latency - ErrandDetailPanel: checkout from completed dependency commit (4j3ZfR_ZX_4rw7j9uj6DV) TypeScript compiles clean. Route uses TanStack Router file-based routing; routeTree.gen.ts auto-regenerated on build. Co-Authored-By: Claude Sonnet 4.6 --- .../web/src/components/CreateErrandDialog.tsx | 142 +++++++ apps/web/src/components/ErrandDetailPanel.tsx | 379 ++++++++++++++++++ apps/web/src/layouts/AppLayout.tsx | 14 +- apps/web/src/routes/errands/index.tsx | 130 ++++++ 4 files changed, 660 insertions(+), 5 deletions(-) create mode 100644 apps/web/src/components/CreateErrandDialog.tsx create mode 100644 apps/web/src/components/ErrandDetailPanel.tsx create mode 100644 apps/web/src/routes/errands/index.tsx diff --git a/apps/web/src/components/CreateErrandDialog.tsx b/apps/web/src/components/CreateErrandDialog.tsx new file mode 100644 index 0000000..0e76e17 --- /dev/null +++ b/apps/web/src/components/CreateErrandDialog.tsx @@ -0,0 +1,142 @@ +import { useEffect, useState } from 'react'; +import { useNavigate } from '@tanstack/react-router'; +import { + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, +} from '@/components/ui/dialog'; +import { Button } from '@/components/ui/button'; +import { Input } from '@/components/ui/input'; +import { Label } from '@/components/ui/label'; +import { Textarea } from '@/components/ui/textarea'; +import { toast } from 'sonner'; +import { cn } from '@/lib/utils'; +import { trpc } from '@/lib/trpc'; + +interface CreateErrandDialogProps { + open: boolean; + onOpenChange: (open: boolean) => void; +} + +export function CreateErrandDialog({ open, onOpenChange }: CreateErrandDialogProps) { + const [description, setDescription] = useState(''); + const [projectId, setProjectId] = useState(''); + const [baseBranch, setBaseBranch] = useState(''); + const [error, setError] = useState(null); + + const navigate = useNavigate(); + const utils = trpc.useUtils(); + + const projectsQuery = trpc.listProjects.useQuery(); + + const createMutation = trpc.errand.create.useMutation({ + onSuccess: (data) => { + toast.success('Errand started'); + onOpenChange(false); + utils.errand.list.invalidate(); + navigate({ to: '/errands', search: { selected: data.id } }); + }, + onError: (err) => { + setError(err.message); + }, + }); + + useEffect(() => { + if (open) { + setDescription(''); + setProjectId(''); + setBaseBranch(''); + setError(null); + } + }, [open]); + + function handleSubmit(e: React.FormEvent) { + e.preventDefault(); + setError(null); + createMutation.mutate({ + description: description.trim(), + projectId, + baseBranch: baseBranch.trim() || undefined, + }); + } + + const canSubmit = + description.trim().length > 0 && + description.length <= 200 && + projectId !== '' && + !createMutation.isPending; + + return ( + + + + New Errand + + Start a small isolated change with a dedicated agent. + + +
+
+ +