+ >
+ );
+}
+
const iframeStyle: React.CSSProperties = {
flex: 1,
width: "100%",
diff --git a/vibn-frontend/app/api/chat/route.ts b/vibn-frontend/app/api/chat/route.ts
index f490c1b..5d214b9 100644
--- a/vibn-frontend/app/api/chat/route.ts
+++ b/vibn-frontend/app/api/chat/route.ts
@@ -217,6 +217,12 @@ Each project has a persistent \`vibn-dev\` container. Edit files via \`fs_*\` an
**Testing Auth & Protected Routes:** Do NOT attempt to verify signup flows or authenticated routes by making HTTP requests (e.g. \`curl\` or \`http_fetch\`) to the dev server yourself. The app is protected by NextAuth or similar session cookies which you do not have. Just write the code, start the dev server via \`dev_server_start\`, and provide the user the clickable \`previewUrl\` so they can test it themselves in their browser. If you hit a redirect/401, do NOT assume the server is broken and loop on restarting it.
+**Design Critique / Visual QA Tool:**
+- \`request_visual_qa { targetPath }\` runs a fast background AI agent to critique a UI file (like \`page.tsx\`, \`layout.tsx\`, or \`.css\`) against a strict 5-dimensional design rubric (Layout, Spacing, Contrast, Hierarchy, Responsiveness).
+- You MUST call this tool whenever your turn involves creating or heavily modifying visual User Interface code before you return the \`previewUrl\` to the user.
+- If the tool returns a failure with actionable issues (e.g., "missing mobile padding" or "using hardcoded colors instead of CSS variables"), you MUST use \`fs_edit\` to fix those specific issues before ending your turn.
+- Do NOT use this tool if you only modified backend code, SQL, config files, or non-visual logic.
+
**Rules:**
- Stay under \`/workspace\`. \`fs_*\` enforce this; use \`shell_exec\` deliberately for system paths.
- Dev container has no route to internal Vibn services (vibn-postgres, etc.) by design.
diff --git a/vibn-frontend/app/api/mcp/route.ts b/vibn-frontend/app/api/mcp/route.ts
index d9060d6..6cade17 100644
--- a/vibn-frontend/app/api/mcp/route.ts
+++ b/vibn-frontend/app/api/mcp/route.ts
@@ -40,6 +40,7 @@ import {
} from "@/lib/workspace-gcs";
import { VIBN_GCS_LOCATION } from "@/lib/gcp/storage";
import { getApplicationRuntimeLogs } from "@/lib/coolify-logs";
+import { callVibnChat } from "@/lib/ai/vibn-chat-model";
import { execInCoolifyApp } from "@/lib/coolify-exec";
import { isCoolifySshConfigured, runOnCoolifyHost } from "@/lib/coolify-ssh";
import {
@@ -230,6 +231,7 @@ export async function GET() {
"browser.console",
"browser.navigate",
"ship",
+ "request_visual_qa",
],
},
},
@@ -414,6 +416,8 @@ export async function POST(request: Request) {
return await toolShellExec(principal, params);
case "fs.read":
return await toolFsRead(principal, params);
+ case "request_visual_qa":
+ return await toolRequestVisualQA(principal, params);
case "fs.write":
return await toolFsWrite(principal, params);
case "fs.edit":
@@ -4485,6 +4489,80 @@ async function runFsCmd(
};
}
+async function toolRequestVisualQA(
+ principal: Principal,
+ params: Record,
+) {
+ const guard = await pathBGuard();
+ if (guard) return guard;
+ const project = await resolveProjectOr404(principal, params);
+ if (project instanceof NextResponse) return project;
+
+ const targetPath = String(params.targetPath ?? "").trim();
+ if (!targetPath) {
+ return NextResponse.json(
+ { error: 'Param "targetPath" is required' },
+ { status: 400 },
+ );
+ }
+
+ const absPath = normalizeFsPath(targetPath);
+ if (absPath instanceof NextResponse) return absPath;
+
+ const r = await runFsCmd(
+ principal,
+ project,
+ `test -f ${shq(absPath)} && cat ${shq(absPath)}`,
+ 10000,
+ );
+
+ if (r.code !== 0) {
+ return NextResponse.json({
+ error: `Could not read file ${targetPath}. Ensure the path is correct and the file exists.`,
+ });
+ }
+
+ const fileContent = r.stdout;
+
+ const prompt = `You are a strict, world-class Senior Design QA Engineer.
+Your job is to evaluate the provided UI code against a strict 5-dimensional rubric and catch "AI slop" before the user sees it.
+If the code is flawless and follows the design system perfectly, say "PASS".
+If it fails ANY criteria, list the exact issues and how to fix them. DO NOT rewrite the whole file, just give actionable critique.
+
+## The Rubric (5 Dimensions)
+1. Layout & Composition: Are there overlapping text elements? Is the grid broken on mobile? Do containers overflow unexpectedly?
+2. Spacing & Padding: Is there enough breathing room? Are paddings consistent? (e.g., tight items should have 4-8px gap, sections should have 24-48px).
+3. Contrast & Color: Are hardcoded hex colors (e.g. #FF0000) used instead of Design System CSS variables (e.g. var(--accent), var(--fg))? ALL colors MUST use CSS variables if a design system is active.
+4. Hierarchy: Is the primary action obvious? Is secondary text properly muted?
+5. Responsiveness: Does the UI stack gracefully on mobile (max-width 760px)? Are \`flex-col\` or \`grid-cols-1\` applied at the right breakpoints?
+
+## The Code
+\`\`\`
+${fileContent.slice(0, 15000)}
+\`\`\`
+
+Evaluate the code. Reply with "PASS" or a concise bulleted list of fixes.`;
+
+ try {
+ const aiResponse = await callVibnChat({
+ systemPrompt: prompt,
+ messages: [{ role: "user", content: "Perform the Visual QA critique." }],
+ temperature: 0.1,
+ });
+
+ return NextResponse.json({
+ result: {
+ critique: aiResponse.text || "No feedback generated.",
+ note: "If this returned issues, you MUST fix them using fs.edit before declaring your turn complete.",
+ },
+ });
+ } catch (err: any) {
+ return NextResponse.json({
+ error: `QA Agent failed: ${err.message}`,
+ });
+ }
+}
+
async function toolFsRead(principal: Principal, params: Record) {
const guard = await pathBGuard();
if (guard) return guard;
diff --git a/vibn-frontend/lib/ai/vibn-tools.ts b/vibn-frontend/lib/ai/vibn-tools.ts
index 9b0da26..f015cb6 100644
--- a/vibn-frontend/lib/ai/vibn-tools.ts
+++ b/vibn-frontend/lib/ai/vibn-tools.ts
@@ -1449,6 +1449,23 @@ After this returns, ALWAYS call apps_deploy { uuid } to regenerate the live Trae
},
},
+ {
+ name: "request_visual_qa",
+ description:
+ "Runs a fast background AI agent to critique a UI file (like page.tsx or .css) against a strict 5-dimensional design rubric. Use this before finishing any turn that involves visual changes.",
+ parameters: {
+ type: "OBJECT",
+ properties: {
+ targetPath: {
+ type: "STRING",
+ description:
+ "The path of the file to critique, e.g. apps/web/app/page.tsx",
+ },
+ },
+ required: ["targetPath"],
+ },
+ },
+
// ── Path B: ship to production ─────────────────────────────────────────────
{