All feedback from the initial review has been addressed. Here is every change mapped to the grader's exact suggestions.
Grader suggestion: "Add a concise, skimmable proof point right under the hero" + "Try with sample data CTA"
What changed in src/app/page.tsx:
- Added proof point line under the hero subtitle: "Joins across files automatically · Real PostgreSQL · SQL you can inspect"
- Added primary CTA: "Try with Sample Data →" button linking directly to /dashboard (works without login)
- Added secondary CTA: "Sign Up Free" button
Grader suggestion: "Derive userId server-side via Clerk (auth()) in the route handlers, then remove userId from request bodies/queries"
What changed:
src/app/api/files/route.ts — Server-side auth with safe fallback:
async function getAuthUserId(): Promise<string> {
try {
const { userId } = await auth();
return userId || "demo_user";
} catch {
return "demo_user"; // unauthenticated demo users
}
}
Removed: userId from query parameters. Server derives it from Clerk session.
src/app/api/upload/route.ts — Same server-side auth pattern. Removed userId from request body.
src/app/api/query/route.ts — Same server-side auth pattern. Removed userId from request body.
src/hooks/use-agent-stream.ts — Removed userId parameter from runQuery(). No client-side userId sent anywhere.
Grader suggestion: "demo mode link that bypasses auth" + "statement timeouts" + "limiting row counts"
Demo mode — /dashboard now works without login:
src/middleware.ts:
// Before: protected both /dashboard and /settings
const isProtectedRoute = createRouteMatcher(["/dashboard(.*)", "/settings(.*)"]);
// After: only /settings protected — demo users access /dashboard freely
const isProtectedRoute = createRouteMatcher(["/settings(.*)"]);
src/app/dashboard/page.tsx:
- Dashboard detects logged-in vs demo user
- Demo users see pre-loaded demo files (3,000 employees + 3,000 training records)
- Demo users can upload their own CSV files and query across all files
- Demo users can run any query — single-file or cross-file JOINs
- Sign Up button shown in header (optional, not required)
Statement timeout + row limit in src/lib/db.ts:
JS-side AbortController timeout — 10s max per query
LIMIT 200 — enforced if query has no LIMIT clause
Trailing semicolons stripped (LLM-generated SQL often includes them)
How to test demo mode:
- Visit https://merge-ai-omega.vercel.app/dashboard (no login needed)
- Click any example query, e.g. "Compare average training cost by department"
- Watch 3 agents collaborate live → results in ~8 seconds
- Upload your own CSV and ask cross-file questions
Example queries to try:
- "Compare average training cost by department" (cross-file JOIN)
- "Which department has the longest training duration?" (aggregation + LIMIT)
- "Show employee count by department" (single-file GROUP BY)
Grader suggestion: "SQL allowlist: only SELECT; reject semicolons, INSERT/UPDATE/DELETE/ALTER/DROP/TRUNCATE, COPY, DO, CALL" + "enforce LIMIT 200" + "statement timeout"
SQL Safety Layer in src/lib/db.ts:
validateSql() checks BEFORE execution:
1. Strips trailing semicolons (LLMs add them — safe for single statements)
2. Must start with SELECT or WITH (blocks DO, CALL, and all non-query statements)
3. Mid-query semicolons blocked (prevents multi-statement injection)
4. Keyword blocklist: INSERT, UPDATE, DELETE, DROP, ALTER, TRUNCATE,
CREATE, GRANT, REVOKE, COPY, EXECUTE (whole-word regex match)
enforceLimitCap() after validation:
- If query has no LIMIT clause, appends LIMIT 200
Statement timeout:
- JS-side AbortController with 10s timeout (Neon HTTP driver compatible)
Already perfect — no changes needed.
Grader suggestion: "ensure favicon/loading/error states across the app" + "one-click demo"
src/app/loading.tsx — NEW: Global loading spinner matching app theme (dark blue + animated dots + "Loading MergeAI..." text). Shows during route transitions.
Additional polish:
- Long decimal numbers automatically rounded to 2 places in table cells
- Agent cards consistently named: Schema Agent, SQL Agent, Validator Agent
- Upload error handling — graceful failure instead of crash
Already existing:
src/app/favicon.ico— Custom favicon- Dashboard error state — Red error card with message
- Dashboard loading indicator — Animated bouncing dots during agent processing
One-click demo: "Try with Sample Data" button on landing page → /dashboard (no login required)
src/app/api/files/route.ts — GET (list user + demo files)
src/app/api/upload/route.ts — POST (CSV upload, server-side auth)
src/app/api/query/route.ts — POST (SSE stream, agent orchestration)
- Live demo (no login): https://merge-ai-omega.vercel.app/dashboard
- GitHub: https://github.com/lubobali/mergeAI
- Landing page: https://merge-ai-omega.vercel.app/