Skip to content

Commit 28e2972

Browse files
feat: responsive design, close PR/discussion APIs, agent stats, leaderboard, Prisma indexes, style polish
Backend: - PATCH /api/discussions/:id — close/reopen/resolve discussions - PATCH /api/pull-requests/:id — close PR without merging - GET /api/agents/:id/stats — per-agent activity breakdown (repos, PRs, reviews, commits) - Added updateDiscussionStatus(), closePullRequest(), getAgentStats() in forge.ts Database: - Added @@index on all FK columns (Repository.ownerId, PullRequest.repositoryId/authorId, Discussion.repositoryId/authorId, GitCommit.repositoryId/authorId, DiscussionMessage.discussionId/authorId) - Added composite index [status, updatedAt] on Repository - Added composite index [repositoryId, status] on PullRequest - Pushed to Neon Postgres Dashboard: - Agent panel is now a sorted leaderboard (by score desc) with rank badges - Close PR panel and Manage Discussion Status panel added to command grid - Resolve/Archive/Reopen discussion buttons Styles & Responsiveness: - 480px mobile breakpoint with 2-col metrics, single-col grids, compact padding - Card hover effects with translateY and glow borders - Event dot pulse animation on latest event - Custom scrollbar styling for event/discussion lists - Focus-visible states for accessibility - Smooth input focus transitions with glow - Print stylesheet (hides ambient, buttons, toasts) - prefers-reduced-motion: disables all animations - Status badge colors (OPEN/MERGED/CLOSED/RESOLVED)
1 parent 5862b2a commit 28e2972

8 files changed

Lines changed: 579 additions & 4 deletions

File tree

prisma/schema.prisma

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,9 @@ model Agent {
3939
designBias String
4040
score Int @default(0)
4141
inventions String[]
42+
43+
// Leaderboard queries
44+
// @@index([score]) -- uncomment if leaderboard queries are slow
4245
ownedRepos Repository[] @relation("RepositoryOwner")
4346
pullRequests PullRequest[] @relation("PullRequestAuthor")
4447
reviews PullRequestReview[] @relation("ReviewAuthor")
@@ -68,6 +71,9 @@ model Repository {
6871
commits GitCommit[]
6972
createdAt DateTime @default(now())
7073
updatedAt DateTime @updatedAt
74+
75+
@@index([ownerId])
76+
@@index([status, updatedAt])
7177
}
7278

7379
model PullRequest {
@@ -86,6 +92,9 @@ model PullRequest {
8692
events AuditEvent[] @relation("PullRequestEvents")
8793
createdAt DateTime @default(now())
8894
updatedAt DateTime @updatedAt
95+
96+
@@index([repositoryId, status])
97+
@@index([authorId])
8998
}
9099

91100
model PullRequestReview {
@@ -113,6 +122,9 @@ model Discussion {
113122
messages DiscussionMessage[]
114123
createdAt DateTime @default(now())
115124
updatedAt DateTime @updatedAt
125+
126+
@@index([repositoryId])
127+
@@index([authorId])
116128
}
117129

118130
model DiscussionMessage {
@@ -123,6 +135,9 @@ model DiscussionMessage {
123135
author Agent @relation("MessageAuthor", fields: [authorId], references: [id])
124136
text String
125137
createdAt DateTime @default(now())
138+
139+
@@index([discussionId])
140+
@@index([authorId])
126141
}
127142

128143
model GitCommit {
@@ -137,6 +152,9 @@ model GitCommit {
137152
language String?
138153
stackDelta Json?
139154
createdAt DateTime @default(now())
155+
156+
@@index([repositoryId, createdAt])
157+
@@index([authorId])
140158
}
141159

142160
model AuditEvent {
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import { NextResponse } from "next/server";
2+
3+
import { getCurrentObserver } from "@/lib/clerk-auth";
4+
import { getAgentStats } from "@/lib/forge";
5+
6+
export const runtime = "nodejs";
7+
8+
export async function GET(_request: Request, context: { params: Promise<{ agentId: string }> }) {
9+
const observer = await getCurrentObserver();
10+
if (!observer) {
11+
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
12+
}
13+
14+
const { agentId } = await context.params;
15+
const result = await getAgentStats(agentId);
16+
17+
if (!result) {
18+
return NextResponse.json({ error: "Agent not found" }, { status: 404 });
19+
}
20+
21+
return NextResponse.json(result);
22+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import { NextResponse } from "next/server";
2+
3+
import { getCurrentObserver } from "@/lib/clerk-auth";
4+
import { updateDiscussionStatus } from "@/lib/forge";
5+
import { updateDiscussionStatusSchema } from "@/lib/schemas";
6+
7+
export const runtime = "nodejs";
8+
9+
export async function PATCH(request: Request, context: { params: Promise<{ discussionId: string }> }) {
10+
const observer = await getCurrentObserver();
11+
if (!observer) {
12+
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
13+
}
14+
15+
const { discussionId } = await context.params;
16+
const body = await request.json();
17+
const parsed = updateDiscussionStatusSchema.safeParse(body);
18+
19+
if (!parsed.success) {
20+
return NextResponse.json({ error: parsed.error.flatten() }, { status: 400 });
21+
}
22+
23+
try {
24+
const discussion = await updateDiscussionStatus(discussionId, parsed.data);
25+
return NextResponse.json(discussion);
26+
} catch (error: unknown) {
27+
return NextResponse.json(
28+
{ error: error instanceof Error ? error.message : "Unknown error" },
29+
{ status: 404 },
30+
);
31+
}
32+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import { NextResponse } from "next/server";
2+
3+
import { getCurrentObserver } from "@/lib/clerk-auth";
4+
import { closePullRequest } from "@/lib/forge";
5+
import { closePullRequestSchema } from "@/lib/schemas";
6+
7+
export const runtime = "nodejs";
8+
9+
export async function PATCH(request: Request, context: { params: Promise<{ pullRequestId: string }> }) {
10+
const observer = await getCurrentObserver();
11+
if (!observer) {
12+
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
13+
}
14+
15+
const { pullRequestId } = await context.params;
16+
const body = await request.json();
17+
const parsed = closePullRequestSchema.safeParse(body);
18+
19+
if (!parsed.success) {
20+
return NextResponse.json({ error: parsed.error.flatten() }, { status: 400 });
21+
}
22+
23+
try {
24+
const pullRequest = await closePullRequest(pullRequestId, parsed.data);
25+
return NextResponse.json(pullRequest);
26+
} catch (error: unknown) {
27+
return NextResponse.json(
28+
{ error: error instanceof Error ? error.message : "Unknown error" },
29+
{ status: error instanceof Error && error.message.includes("not found") ? 404 : 409 },
30+
);
31+
}
32+
}

0 commit comments

Comments
 (0)