Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
# misc
.DS_Store
*.pem
desktop.ini

# debug
npm-debug.log*
Expand All @@ -39,3 +40,10 @@ yarn-error.log*
# typescript
*.tsbuildinfo
next-env.d.ts

# IDE
.vscode/settings.json

# OS
**/ai-engineering-platform
**/ai-engineering-platform - Copy
Empty file added Code
Empty file.
Empty file added Open
Empty file.
Empty file added VS
Empty file.
Empty file added in
Empty file.
Empty file added project
Empty file.
5 changes: 2 additions & 3 deletions src/app/auth/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,9 @@

import { useState } from "react";
import Link from "next/link";
import { getBrowserSupabase } from "@/lib/supabase/client";

import { createClient } from "@/lib/supabase/client";
export default function AuthPage() {
const supabase = getBrowserSupabase();
const supabase = createClient();
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const [status, setStatus] = useState("");
Expand Down
Empty file added src/app/auth/page.tsx#
Empty file.
Empty file added src/app/auth/page.tsxcode
Empty file.
78 changes: 78 additions & 0 deletions src/app/login/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
"use client";

import { useState } from "react";
import { createClient } from "@/lib/supabase/client";
import { useRouter } from "next/navigation";

export default function LoginPage() {
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const [loading, setLoading] = useState(false);
const [error, setError] = useState("");
const router = useRouter();
const supabase = createClient();

const handleLogin = async (e: React.FormEvent) => {
e.preventDefault();
setLoading(true);
setError("");

const { error } = await supabase.auth.signInWithPassword({

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Guard the nullable Supabase client in login

Fresh evidence after the missing-key fallback fix: createClient() now returns null when NEXT_PUBLIC_SUPABASE_URL or NEXT_PUBLIC_SUPABASE_ANON_KEY is absent, but this login handler still dereferences it. Submitting /login in the local/demo missing-key state throws before the page can show an error, unlike /signup which already guards this case; add the same null check here.

Useful? React with 👍 / 👎.

email,
password,
});

if (error) {
setError(error.message);
setLoading(false);
} else {
router.push("/");
}
};

return (
<div className="min-h-screen flex items-center justify-center bg-[#0F1419]">
<div className="bg-[#1A1F2E] p-8 rounded-lg border border-[#2D3748] w-full max-w-md">
<h1 className="text-2xl font-bold text-white mb-6">AI Engineering Platform</h1>
<form onSubmit={handleLogin} className="space-y-4">
<div>
<label className="block text-sm text-[#A0AEC0] mb-1">Email</label>
<input
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
className="w-full px-4 py-2 bg-[#0F1419] border border-[#2D3748] rounded text-white focus:outline-none focus:border-[#00D4FF]"
required
/>
</div>
<div>
<label className="block text-sm text-[#A0AEC0] mb-1">Password</label>
<input
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
className="w-full px-4 py-2 bg-[#0F1419] border border-[#2D3748] rounded text-white focus:outline-none focus:border-[#00D4FF]"
required
/>
</div>
{error && (
<div className="text-red-500 text-sm">{error}</div>
)}
<button
type="submit"
disabled={loading}
className="w-full py-2 bg-[#00D4FF] text-[#0F1419] font-semibold rounded hover:bg-[#00D4FF]/80 transition disabled:opacity-50"
>
{loading ? "Loading..." : "Sign In"}
</button>
</form>
<p className="mt-4 text-center text-sm text-[#A0AEC0]">
Don't have an account?{" "}

Check failure on line 70 in src/app/login/page.tsx

View workflow job for this annotation

GitHub Actions / Validate Next.js app

`'` can be escaped with `&apos;`, `&lsquo;`, `&#39;`, `&rsquo;`
<a href="/signup" className="text-[#00D4FF] hover:underline">
Sign Up
</a>
</p>
</div>
</div>
);
}
4 changes: 1 addition & 3 deletions src/app/page.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
import Dashboard from "@/components/dashboard/Dashboard";

export default function Home() {
export default function Home)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Restore valid Home component syntax

This change leaves the root page as invalid TSX: export default function Home) is missing the parameter list braces and function body opener, so compiling or serving / fails before the dashboard can render. The parent had a valid Home() { ... } component, so restore the function declaration and closing brace.

Useful? React with 👍 / 👎.

return <Dashboard />;
}
}
113 changes: 113 additions & 0 deletions src/app/signup/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
"use client";

import { useState } from "react";
import { createClient } from "@/lib/supabase/client";
import { useRouter } from "next/navigation";

export default function SignupPage() {
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const [loading, setLoading] = useState(false);
const [error, setError] = useState("");
const [confirmationSent, setConfirmationSent] = useState(false);
const router = useRouter();
const supabase = createClient();

const handleSignup = async (e: React.FormEvent) => {
e.preventDefault();
setLoading(true);
setError("");

if (!supabase) {
setError("Supabase is not configured. Please check your environment variables.");
setLoading(false);
return;
}

const { data, error } = await supabase.auth.signUp({
email,
password,
});

if (error) {
setError(error.message);
setLoading(false);
} else {
// Check if email confirmation is required
if (data?.user && !data.session) {
setConfirmationSent(true);
setLoading(false);
} else if (data?.session) {
// User is immediately signed in, redirect to home
router.push("/");
}
}
};

if (confirmationSent) {
return (
<div className="min-h-screen flex items-center justify-center bg-[#0F1419]">
<div className="bg-[#1A1F2E] p-8 rounded-lg border border-[#2D3748] w-full max-w-md text-center">
<h1 className="text-2xl font-bold text-white mb-4">Check Your Email</h1>
<p className="text-[#A0AEC0] mb-4">
We've sent a confirmation link to <strong>{email}</strong>. Please check your email and click the link to confirm your account.

Check failure on line 53 in src/app/signup/page.tsx

View workflow job for this annotation

GitHub Actions / Validate Next.js app

`'` can be escaped with `&apos;`, `&lsquo;`, `&#39;`, `&rsquo;`
</p>
<button
onClick={() => {
setConfirmationSent(false);
setEmail("");
setPassword("");
}}
className="w-full py-2 bg-[#00D4FF] text-[#0F1419] font-semibold rounded hover:bg-[#00D4FF]/80 transition"
>
Back to Signup
</button>
</div>
</div>
);
}

return (
<div className="min-h-screen flex items-center justify-center bg-[#0F1419]">
<div className="bg-[#1A1F2E] p-8 rounded-lg border border-[#2D3748] w-full max-w-md">
<h1 className="text-2xl font-bold text-white mb-6">Create Account</h1>
<form onSubmit={handleSignup} className="space-y-4">
<div>
<label className="block text-sm text-[#A0AEC0] mb-1">Email</label>
<input
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
className="w-full px-4 py-2 bg-[#0F1419] border border-[#2D3748] rounded text-white focus:outline-none focus:border-[#00D4FF]"
required
/>
</div>
<div>
<label className="block text-sm text-[#A0AEC0] mb-1">Password</label>
<input
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
className="w-full px-4 py-2 bg-[#0F1419] border border-[#2D3748] rounded text-white focus:outline-none focus:border-[#00D4FF]"
required
/>
</div>
{error && <div className="text-red-500 text-sm">{error}</div>}
<button
type="submit"
disabled={loading}
className="w-full py-2 bg-[#00D4FF] text-[#0F1419] font-semibold rounded hover:bg-[#00D4FF]/80 transition disabled:opacity-50"
>
{loading ? "Loading..." : "Sign Up"}
</button>
</form>
<p className="mt-4 text-center text-sm text-[#A0AEC0]">
Already have an account?{" "}
<a href="/login" className="text-[#00D4FF] hover:underline">
Sign In
</a>
</p>
</div>
</div>
);
}
10 changes: 5 additions & 5 deletions src/components/dashboard/Dashboard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ const activity = ["Governance branch verified", "Design tokens installed", "Miss
{ id: "mission-1", title: "Supabase Persistence Layer", objective: "Persist generated missions, outputs, and approval state into Supabase with scoped access.", status: "In Progress" as const, complexity: "High" as const, progress: 68, completedCount: 7, nextMilestone: 10 },
{ id: "mission-2", title: "Mission Evaluation Engine", objective: "Score mission outputs across accuracy, completeness, usability, risk, and format compliance.", status: "Review" as const, complexity: "Medium" as const, progress: 82, completedCount: 8, nextMilestone: 10 },
{ id: "mission-3", title: "Workflow Library Index", objective: "Create a reusable library for saved mission patterns, prompts, and operational playbooks.", status: "Approved" as const, complexity: "Medium" as const, progress: 90, completedCount: 9, nextMilestone: 10 },
];
]];

const activity = ["Schema imported into Supabase", "Mission workflow shell committed", "Settings page verified environment keys", "Dashboard route refreshed", "Approval policy registered"];

Expand All @@ -30,8 +30,7 @@ export default function Dashboard() {
const [toastVisible, setToastVisible] = useState(true);

return (
<div className="min-h-screen bg-[var(--bg-primary)] font-sans text-[var(--text-primary)]">
<div className="min-h-screen bg-[var(--bg-primary)] font-['Inter'] text-[var(--text-primary)]">
<div className="min-h-screen bg-[var(--bg-primary)] font-sans text-[var(--text-primary)]"> <div className="min-h-screen bg-[var(--bg-primary)] font-['Inter'] text-[var(--text-primary)]">
<div className="flex">
<Sidebar currentPath="/" />
<div className="flex min-h-screen flex-1 flex-col">
Expand All @@ -58,7 +57,8 @@ export default function Dashboard() {
<h2 className="mt-4 text-[48px] font-bold leading-[1.1]">Operational Mission Control</h2>
<div className="mt-6 grid gap-4 md:grid-cols-3">
<StatusIndicator status="24 missions deployed" tone="success" />
<StatusIndicator status="96% success rate" tone="info" />
<StatusIndicator status="96% success ra-[;
mjku . te" tone="info" />
Comment on lines +60 to +61

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Restore the success-rate label

When the dashboard renders, this prop displays 96% success ra-[; mjku . te instead of the success-rate label, exposing accidental text on the primary MVP dashboard and making the status metric unreadable. Restore the intended 96% success rate text.

AGENTS.md reference: AGENTS.md:L25-L27

Useful? React with 👍 / 👎.

<StatusIndicator status="96% approval rate" tone="info" />
<StatusIndicator status="12 day streak" tone="inProgress" />
</div>
Expand All @@ -84,7 +84,7 @@ export default function Dashboard() {
<section className="mb-8 grid gap-6 xl:grid-cols-[1fr_320px]">
<div>
<h3 className="mb-6 text-[24px] font-semibold leading-[1.3]">Active Missions</h3>
<div className="grid gap-6 md:grid-cols-2 2xl:grid-cols-3">
<div className="grid gap-6 md:grid-cols-2 2xl:grid-cols-3">NM

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Remove the stray mission-grid text

When the Active Missions section renders, the literal NM is emitted as a visible text node immediately before the mission cards. Remove this accidental text so the primary dashboard does not show unexplained characters.

AGENTS.md reference: AGENTS.md:L25-L27

Useful? React with 👍 / 👎.

{missions.map((mission) => <MissionCard key={mission.id} {...mission} />)}
</div>
</div>
Expand Down
12 changes: 9 additions & 3 deletions src/lib/supabase/client.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,18 @@
import { createClient } from "@supabase/supabase-js";
import { createBrowserClient } from "@supabase/ssr";

export function getBrowserSupabase() {
export function createClient() {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Keep getBrowserSupabase exported

When this replacement lands, existing modules still import getBrowserSupabase (src/app/auth/page.tsx and src/lib/database/missions.ts; searched repo-wide for getBrowserSupabase). Those routes/features will fail to build/load because this file no longer exports the symbol, so keep a compatibility export or update all callers in the same change.

Useful? React with 👍 / 👎.

const url = process.env.NEXT_PUBLIC_SUPABASE_URL;
const anonKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY;

// Return null if keys are missing to allow fallback/error UI
if (!url || !anonKey) {
return null;
}

return createClient(url, anonKey);
return createBrowserClient(url, anonKey);
}

// Compatibility export for existing imports
export function getBrowserSupabase() {
return createClient();
}
31 changes: 31 additions & 0 deletions src/lib/supabase/server.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { createServerClient } from "@supabase/ssr";
import { cookies } from "next/headers";

export async function createClient() {
const cookieStore = await cookies();

// Return null if keys are missing
const url = process.env.NEXT_PUBLIC_SUPABASE_URL;
const anonKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY;

if (!url || !anonKey) {
return null;
}

return createServerClient(url, anonKey, {
cookies: {
getAll() {
return cookieStore.getAll();
},
setAll(cookiesToSet) {
try {
cookiesToSet.forEach(({ name, value, options }) =>
cookieStore.set(name, value, options)
);
} catch {
// Handle error
}
},
},
});
}
62 changes: 62 additions & 0 deletions src/middleware.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import { type NextRequest } from "next/server";
import { createServerClient } from "@supabase/ssr";
import { NextResponse } from "next/server";

export async function middleware(request: NextRequest) {
// Skip middleware if Supabase keys are not configured
const url = process.env.NEXT_PUBLIC_SUPABASE_URL;
const anonKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY;

if (!url || !anonKey) {
return NextResponse.next({ request });
}

// Return JSON error for API routes instead of redirecting
if (request.nextUrl.pathname.startsWith("/api/")) {
// Let the API route handle auth via createServerClient
return NextResponse.next({ request });
Comment on lines +15 to +17

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Require auth before bypassing API routes

In deployments with OPENAI_API_KEY configured, this makes every /api/* request skip the only new auth check, but I checked src/app/api/mission/generate/route.ts and it does not authenticate before calling client.responses.create. That leaves the paid mission-generation endpoint callable by unauthenticated clients even though the pages are protected, so either enforce a JSON 401 here or add equivalent auth inside the route before bypassing middleware redirects.

Useful? React with 👍 / 👎.

}

let supabaseResponse = NextResponse.next({
request,
});

const supabase = createServerClient(url, anonKey, {
cookies: {
getAll() {
return request.cookies.getAll();
},
setAll(cookiesToSet) {
cookiesToSet.forEach(({ name, value, options }) =>

Check warning on line 30 in src/middleware.ts

View workflow job for this annotation

GitHub Actions / Validate Next.js app

'options' is defined but never used
request.cookies.set(name, value)
);
supabaseResponse = NextResponse.next({
request,
});
cookiesToSet.forEach(({ name, value, options }) =>
supabaseResponse.cookies.set(name, value, options)
);
},
},
});

const {
data: { user },
} = await supabase.auth.getUser();

// If no user and trying to access protected route, redirect to login
// But allow signup page for unauthenticated users
if (!user && !request.nextUrl.pathname.startsWith("/login") && !request.nextUrl.pathname.startsWith("/signup")) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Keep the existing auth page reachable

This commit still edits and ships src/app/auth/page.tsx, but the new middleware only treats /login and /signup as public. A signed-out user who opens the existing /auth sign-in/sign-up page now gets redirected to /login, making that route's auth UI unreachable unless the user is already authenticated; either add /auth to the public-route check or remove/update the legacy route intentionally.

Useful? React with 👍 / 👎.

const url = request.nextUrl.clone();
url.pathname = "/login";

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve the protected destination through login

When a signed-out user opens a protected deep link such as /missions/new, this redirect replaces the pathname without recording the original destination, and src/app/login/page.tsx always pushes to / after authentication. The user therefore loses the mission route they intended to open and must navigate back manually; include a validated return path and consume it after login.

AGENTS.md reference: AGENTS.md:L25-L28

Useful? React with 👍 / 👎.

return NextResponse.redirect(url);
}

return supabaseResponse;
}

export const config = {
matcher: [
"/((?!_next/static|_next/image|favicon.ico|.*\\.(?:svg|png|jpg|jpeg|gif|webp)$).*)",

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Return JSON errors for unauthenticated API calls

This matcher also runs the auth redirect on /api/mission/generate, while src/app/missions/new/page.tsx fetches that route and immediately parses JSON. If a session expires while the form is open, or an API client posts without a session, the request gets a redirect/login response instead of JSON, so mission creation throws and stays in the loading path; exclude API routes from this page redirect or return a 401 JSON response for them.

Useful? React with 👍 / 👎.

],
};
Empty file added your
Empty file.
Loading