diff --git a/.gitignore b/.gitignore index 3eabc76..d3bebc3 100644 --- a/.gitignore +++ b/.gitignore @@ -30,3 +30,8 @@ pnpm-debug.log* # Supabase supabase/.temp/ + +# Next.js +.next/ +out/ +next-env.d.ts diff --git a/apps/web/.env.example b/apps/web/.env.example new file mode 100644 index 0000000..67bd392 --- /dev/null +++ b/apps/web/.env.example @@ -0,0 +1,7 @@ +# Supabase +NEXT_PUBLIC_SUPABASE_URL=https://your-project.supabase.co +NEXT_PUBLIC_SUPABASE_ANON_KEY=your-anon-key +SUPABASE_SERVICE_ROLE_KEY=your-service-role-key + +# GitHub (temporary -- will be replaced by GitHub App) +GITHUB_TOKEN=ghp_your-token diff --git a/apps/web/.eslintrc.json b/apps/web/.eslintrc.json new file mode 100644 index 0000000..bffb357 --- /dev/null +++ b/apps/web/.eslintrc.json @@ -0,0 +1,3 @@ +{ + "extends": "next/core-web-vitals" +} diff --git a/apps/web/next.config.mjs b/apps/web/next.config.mjs new file mode 100644 index 0000000..81509a0 --- /dev/null +++ b/apps/web/next.config.mjs @@ -0,0 +1,6 @@ +/** @type {import('next').NextConfig} */ +const nextConfig = { + transpilePackages: ['@wright/shared'], +} + +export default nextConfig diff --git a/apps/web/package.json b/apps/web/package.json new file mode 100644 index 0000000..7857e84 --- /dev/null +++ b/apps/web/package.json @@ -0,0 +1,32 @@ +{ + "name": "@wright/web", + "version": "0.1.0", + "private": true, + "type": "module", + "scripts": { + "dev": "next dev --port 3000", + "build": "next build", + "start": "next start", + "lint": "next lint", + "clean": "rm -rf .next .turbo" + }, + "dependencies": { + "@supabase/ssr": "^0.6.0", + "@supabase/supabase-js": "^2.49.0", + "@wright/shared": "workspace:*", + "next": "^14.2.0", + "react": "^18.3.0", + "react-dom": "^18.3.0" + }, + "devDependencies": { + "@types/node": "^22.0.0", + "@types/react": "^18.3.0", + "@types/react-dom": "^18.3.0", + "autoprefixer": "^10.4.0", + "eslint": "^8.57.0", + "eslint-config-next": "^14.2.0", + "postcss": "^8.4.0", + "tailwindcss": "^3.4.0", + "typescript": "^5.7.0" + } +} diff --git a/apps/web/postcss.config.mjs b/apps/web/postcss.config.mjs new file mode 100644 index 0000000..d0c615b --- /dev/null +++ b/apps/web/postcss.config.mjs @@ -0,0 +1,9 @@ +/** @type {import('postcss-load-config').Config} */ +const config = { + plugins: { + tailwindcss: {}, + autoprefixer: {}, + }, +} + +export default config diff --git a/apps/web/src/app/globals.css b/apps/web/src/app/globals.css new file mode 100644 index 0000000..30845d7 --- /dev/null +++ b/apps/web/src/app/globals.css @@ -0,0 +1,25 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; + +:root { + --foreground-rgb: 15, 23, 42; + --background-start-rgb: 248, 250, 252; + --background-end-rgb: 241, 245, 249; +} + +body { + color: rgb(var(--foreground-rgb)); + background: linear-gradient( + to bottom, + transparent, + rgb(var(--background-end-rgb)) + ) + rgb(var(--background-start-rgb)); +} + +@layer utilities { + .text-balance { + text-wrap: balance; + } +} diff --git a/apps/web/src/app/jobs/[id]/page.tsx b/apps/web/src/app/jobs/[id]/page.tsx new file mode 100644 index 0000000..e044550 --- /dev/null +++ b/apps/web/src/app/jobs/[id]/page.tsx @@ -0,0 +1,70 @@ +import Link from 'next/link' + +/** + * Job detail page -- shows status, events timeline, and test results. + * + * This is a scaffold. The full implementation will: + * - Fetch job details from Supabase + * - Subscribe to real-time job_events updates + * - Show test result diffs between loops + * - Link to the created PR + */ +export default function JobDetailPage({ + params, +}: { + params: { id: string } +}) { + return ( +
+ {/* Navigation */} + + +
+
+

Job Details

+ + {params.id} + +
+ + {/* Placeholder for job details */} +
+
+

+ This page will show real-time job status, event timeline, and test + results once connected to Supabase. +

+
+

+ Coming Soon +

+
    +
  • Real-time status updates via Supabase Realtime
  • +
  • Event timeline (claimed, cloning, editing, testing...)
  • +
  • Test results per loop iteration
  • +
  • Cost tracking (API spend per loop)
  • +
  • Link to created PR
  • +
  • Cancel / retry controls
  • +
+
+
+
+
+
+ ) +} diff --git a/apps/web/src/app/layout.tsx b/apps/web/src/app/layout.tsx new file mode 100644 index 0000000..d21b271 --- /dev/null +++ b/apps/web/src/app/layout.tsx @@ -0,0 +1,20 @@ +import type { Metadata } from 'next' +import './globals.css' + +export const metadata: Metadata = { + title: 'Wright — AI Dev Automation', + description: + 'Submit a task, get a pull request. Wright uses Claude to edit code, run tests, and create PRs automatically.', +} + +export default function RootLayout({ + children, +}: { + children: React.ReactNode +}) { + return ( + + {children} + + ) +} diff --git a/apps/web/src/app/new/page.tsx b/apps/web/src/app/new/page.tsx new file mode 100644 index 0000000..807502c --- /dev/null +++ b/apps/web/src/app/new/page.tsx @@ -0,0 +1,236 @@ +'use client' + +import { useState } from 'react' +import Link from 'next/link' +import { submitTask } from '@/lib/actions' + +export default function NewTaskPage() { + const [repoUrl, setRepoUrl] = useState('') + const [task, setTask] = useState('') + const [branch, setBranch] = useState('main') + const [maxLoops, setMaxLoops] = useState(10) + const [maxBudget, setMaxBudget] = useState(5.0) + const [submitting, setSubmitting] = useState(false) + const [result, setResult] = useState<{ + success: boolean + jobId?: string + error?: string + } | null>(null) + + async function handleSubmit(e: React.FormEvent) { + e.preventDefault() + setSubmitting(true) + setResult(null) + + try { + const res = await submitTask({ + repoUrl, + task, + branch, + maxLoops, + maxBudgetUsd: maxBudget, + }) + setResult(res) + } catch (err) { + setResult({ + success: false, + error: err instanceof Error ? err.message : 'Unknown error', + }) + } finally { + setSubmitting(false) + } + } + + return ( +
+ {/* Navigation */} + + +
+

Submit a Task

+

+ Describe what you need done. Wright will clone the repo, make changes, + run tests, and create a PR. +

+ +
+ {/* Repository URL */} +
+ + setRepoUrl(e.target.value)} + className="mt-1 block w-full rounded-lg border border-slate-300 px-4 py-2.5 text-slate-900 placeholder-slate-400 focus:border-wright-500 focus:outline-none focus:ring-1 focus:ring-wright-500" + /> +

+ The GitHub repository to work on. Must be accessible with your + GitHub credentials. +

+
+ + {/* Task Description */} +
+ +