Create the Tessera frontend files in web/src/. The Next.js + shadcn/ui scaffold already exists. Only create TypeScript/TSX files — no bash commands needed. ## Files to create (read existing scaffold files first to understand imports) ### 1. web/src/lib/types.ts Copy the types from the web spec (docs/web-spec.md lines 89-110). Add import for Lucide icons where needed. Create TypeScript interfaces for: Ticket, Queue, Transaction, Scrip, Template, Lifecycle, LifecycleDefinition, CustomField, CustomFieldValue, PreviewResult, PreparedScrip, UpdateResult, ScripResult. ### 2. web/src/lib/api.ts Fetch wrapper using the types. Base URL is '/api' (proxied by next.config.ts to backend). Every function returns Promise<{ data: T | null; error: string | null }>. Import types from './types'. Implement all 15 functions from docs/web-spec.md lines 69-84. Use try/catch with fetch. Parse JSON responses. Handle non-200 status codes. Use backend URL path mapping: /api/tickets, /api/queues, /api/scrips, /api/custom-fields, /api/lifecycles. ### 3. web/src/app/layout.tsx Add dark mode. Import Inter from next/font/google. Set className="dark" on html element. Import and use existing components: no shadcn components needed for layout. Nav bar with gradient and blur. Content area with min-height. ### 4. web/src/app/page.tsx (ticket list) Full ticket list page with: - State: tickets array, queues array, filters (queue_id, status), loading, error - Fetch tickets + queues on mount with useEffect - Filter bar: Select for queue (shadcn Select), Select for status, Filter button - Table using shadcn Table component - Badge for status (shadcn Badge) with colors: new=default, open=blue, in_progress=yellow, resolved=green, closed=gray - Click row → useRouter router.push(`/tickets/${id}`) - Empty state: "No tickets yet" - "New Ticket" button → shadcn Dialog with form (subject input, queue select) - Form uses useState for values, calls createTicket on submit - Loading: "Loading tickets..." text - Error: red text with error message ### 5. web/src/app/tickets/[id]/page.tsx Ticket detail page with: - State: ticket, transactions, loading, error, selectedStatus, previewResult - Back link using Link from next/link - Ticket info: Card with subject, status badge, queue, dates, owner - Status change: Select dropdown, "Preview" button (calls previewTicket, shows in Dialog), "Apply" button (calls updateTicket) - Transaction timeline: Card with list of transactions. Each shows type badge (colored), field change, timestamp using date-fns formatDistanceToNow - Custom fields: Card showing CF name:value pairs (from ticket.custom_fields) ### 6. web/src/app/admin/page.tsx Admin page with shadcn Tabs: Queues, Lifecycles, Scrips, Custom Fields. Each tab: list table + "Add" button → Dialog with form. Uses existing API functions from lib/api.ts. ## Rules - Use shadcn/ui components by importing from '@/components/ui/...' (e.g. '@/components/ui/button') - Use lucide-react for icons (import from 'lucide-react') - All state management with React useState/useEffect - No external state libraries - Proper TypeScript types everywhere - Handle loading and error states - Dark theme is automatic via the dark class on html - Use Tailwind classes for spacing and layout - Minimal styling needed — shadcn handles it ## Verification After creating all files: 1. Run `cd /home/gjermund/projects/tessera/web && nix-shell -p bun --run "bun run build"` to verify no compile errors 2. Fix any type errors 3. Commit after each file