# NeonDB Integration: First-Class Support for Next.js and React/Vite

> Generated by swarm planning session on 2026-02-17

## Summary

Elevate Neon from a Portal-template-only experiment to a first-class database integration on par with Supabase. Users will be able to connect Neon to any Next.js or React/Vite app, with full AI agent support for schema introspection, SQL execution, and code generation — all without requiring the Portal template. Next.js apps use `DATABASE_URL` with server-side code (API routes, Server Actions). React/Vite apps use the Neon Data API — a managed REST proxy with JWT validation and RLS — so no server layer or `DATABASE_URL` exposure is needed.

## Problem Statement

Today, Neon in Dyad is a second-class citizen:

- **Locked to the Portal template**: Only `portal-mini-store` has `requiresNeon: true` — users cannot use Neon with Next.js templates
- **No agent tools**: The AI agent has no `execute_sql`, `get_table_schema`, or `get_project_info` equivalents for Neon
- **No schema introspection**: The agent cannot read Neon table schemas, so it cannot generate correct queries
- **Hub-page-only connector**: `NeonConnector` lives on the hub page, not per-app like `SupabaseConnector`
- **Only creates projects**: No way to link existing Neon projects (only `neon:create-project` exists)

This violates the **Backend-Flexible** design principle — users who want serverless Postgres without a full BaaS (Supabase) have no viable path in Dyad.

## Scope

### In Scope (MVP / v1)

- Decouple Neon from Portal template — works with Next.js and React/Vite templates
- Per-app Neon project selector on the app-details page (matching Supabase's pattern)
- List existing Neon projects + create new projects
- Full branch selector with color-coded badges (production/development/preview)
- Agent tools: `get_neon_project_info`, `get_neon_table_schema`, `execute_neon_sql`
- Neon context in agent prompt with schema awareness
- `add_integration` tool support for `provider: "neon"`
- System prompt covering Drizzle ORM setup, API routes (Next.js), Data API patterns (React/Vite), connection patterns
- Auth guidance: recommend Neon Auth — built-in auth powered by Better Auth, no external providers needed (no homegrown JWT+bcrypt)
- `<dyad-execute-sql>` tag support routed to Neon when Neon is the active provider
- Neon and Supabase mutually exclusive per app
- Full stack example code in system prompt

### Out of Scope (Follow-up / v2)

- Multi-account Neon support (currently single-account; multi-org like Supabase can come later)
- Migration file writing with `enableNeonWriteSqlMigration` flag
- Agent-driven branch creation/deletion
- Connection URI caching (fetch on-demand for v1)

## User Stories

1. **As a user creating a new Next.js or React/Vite app**, I want to connect my Neon account and have the AI agent set up my database schema, so that I get a working database-backed app without manual SQL or connection configuration.

2. **As a user with an existing Neon project**, I want to link it to my Dyad app by selecting it from a dropdown, so that I don't have to create a new project or copy connection strings.

3. **As a user building a CRUD app**, I want to tell the agent "add a tasks table and build a task list page" and have it execute the SQL on Neon and generate the React components, so that I get an end-to-end working feature in one step.

4. **As a user who chose Neon over Supabase**, I want the AI to recommend Neon Auth for authentication, so that I get a secure, production-ready auth solution that lives in my Neon database and branches with it.

5. **As a user working on a feature branch**, I want to select my Neon development branch and have the agent execute SQL against it, so that I can iterate safely without affecting production data.

## UX Design

### User Flow

#### First-Time Connection Flow

1. User creates or opens a Next.js or React/Vite app
2. In the app-details page, user sees **"Connect Neon"** card in the integrations section (same position as SupabaseConnector)
3. User clicks "Connect Neon" → OAuth popup opens in browser
4. User authorizes Dyad → popup closes, tokens stored via deep link
5. Integration card updates to show **project selector dropdown** (grouped by organization)
   - Existing projects listed via `neon:list-projects`
   - "Create New Project" option at the top
6. User selects or creates a project → project linked to app
7. **Branch selector** appears with color-coded badges:
   - 🟢 Production (green)
   - 🔵 Development (blue, default selected)
   - 🟡 Preview (yellow)
8. AI agent now has full Neon context — can execute SQL, read schemas, generate code

#### AI Agent Interaction Flow

1. User asks "I need a database for my todo app"
2. If Neon is NOT connected: agent renders `<dyad-add-integration provider="neon">` prompt
3. If Neon IS connected: agent uses `get_neon_project_info` to check existing tables
4. Agent generates Drizzle schema, executes SQL via `<dyad-execute-sql>` tag
5. Agent generates DB client + API routes / Server Actions (Next.js) or Data API queries (React/Vite) + React components
6. User sees SQL execution results in chat, previews updated app

### Key States

| State                           | Description                                                            | Visual                         |
| ------------------------------- | ---------------------------------------------------------------------- | ------------------------------ |
| **Not Connected**               | "Connect to Neon" card with Neon logo and brief description            | Card with connect button       |
| **Connected, No Project**       | Project selector dropdown visible, loading projects                    | Select component with skeleton |
| **Connected, Project Selected** | Project name in Badge, branch selector visible, "Open in Console" link | Full integration card          |
| **Loading**                     | Skeleton placeholders for project/branch lists                         | Skeleton components            |
| **Error**                       | Red text with retry button and actionable message                      | Error state with CTA           |

### Interaction Details

- **Branch selection**: Changing the branch updates the agent's SQL target (connection string). Toast notification confirms: "Switched to development branch"
- **Project disconnect**: Destructive button with confirmation. Clears `neonProjectId` from app record
- **External link**: "Open in Neon Console" button opens the project in browser
- **Auth context**: When user asks for auth, agent recommends Neon Auth — auth data stored in the Neon database, branches with the database

### Accessibility

- All interactive elements keyboard-focusable (connect button, Select dropdowns, branch selector)
- Branch type badges have `aria-label` (e.g., "production branch", not just color)
- Error messages associated with controls via `aria-describedby`
- Status changes announced via `aria-live` region
- Spinner animations respect `prefers-reduced-motion`

## Technical Design

### Architecture

Two architecture patterns for generated apps, depending on framework:

**Next.js (server-side access via DATABASE_URL):**

```
┌─────────────────────────────────────────────┐
│ Layer 3: Auth (Neon Auth)                    │
├─────────────────────────────────────────────┤
│ Layer 2: Backend                             │
│  Next.js: API Routes / Server Actions        │
├─────────────────────────────────────────────┤
│ Layer 1: Database                            │
│  @neondatabase/serverless + Drizzle ORM      │
│  Connection via DATABASE_URL env var         │
└─────────────────────────────────────────────┘
```

**React/Vite (client-side access via Data API + RLS):**

```
┌─────────────────────────────────────────────┐
│ Layer 3: Auth (Neon Auth)                    │
├─────────────────────────────────────────────┤
│ Layer 2: Data API (managed by Neon)          │
│  REST proxy with JWT validation + RLS        │
│  No DATABASE_URL needed in client code       │
├─────────────────────────────────────────────┤
│ Layer 1: Database                            │
│  PostgreSQL with Row-Level Security          │
└─────────────────────────────────────────────┘
```

Within Dyad itself (management plane):

```
┌─────────────────────────────────────────┐
│ Agent Tools (get_neon_*, execute_sql)    │
├─────────────────────────────────────────┤
│ Neon Context Module (neon_context.ts)    │
├─────────────────────────────────────────┤
│ Neon Management Client (existing)        │
│ @neondatabase/serverless (SQL execution) │
│ @neondatabase/api-client (project CRUD)  │
├─────────────────────────────────────────┤
│ IPC Handlers (neon_handlers.ts)          │
├─────────────────────────────────────────┤
│ Settings Storage (encrypted credentials) │
└─────────────────────────────────────────┘
```

### Components Affected

| Component              | File(s)                                                                | Change Type                                                                                                                                                                                  |
| ---------------------- | ---------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Agent Context          | `src/pro/main/ipc/handlers/local_agent/tools/types.ts`                 | Modify — add `neonProjectId`, `neonDevelopmentBranchId`, `neonActiveBranchId`, `frameworkType`                                                                                               |
| Agent Handler          | `src/pro/main/ipc/handlers/local_agent/local_agent_handler.ts`         | Modify — populate Neon context from `chat.app.*`                                                                                                                                             |
| Tool Definitions       | `src/pro/main/ipc/handlers/local_agent/tool_definitions.ts`            | Modify — register 3 new Neon tools                                                                                                                                                           |
| Add Integration        | `src/pro/main/ipc/handlers/local_agent/tools/add_integration.ts`       | Modify — add `"neon"` to supported providers (Next.js and React/Vite)                                                                                                                        |
| Neon Context           | `src/neon_admin/neon_context.ts`                                       | **New** — `getNeonClientCode()`, `getNeonContext()`, `getNeonProjectInfo()`, `getNeonTableSchema()`, `executeNeonSql()` (extract SQL execution from `src/ipc/utils/neon_timestamp_utils.ts`) |
| Neon Project Info Tool | `src/pro/main/ipc/handlers/local_agent/tools/get_neon_project_info.ts` | **New** — mirrors `get_supabase_project_info.ts`                                                                                                                                             |
| Neon Table Schema Tool | `src/pro/main/ipc/handlers/local_agent/tools/get_neon_table_schema.ts` | **New** — mirrors `get_supabase_table_schema.ts`                                                                                                                                             |
| Execute Neon SQL Tool  | `src/pro/main/ipc/handlers/local_agent/tools/execute_neon_sql.ts`      | **New** — mirrors `execute_sql.ts` with `@neondatabase/serverless`                                                                                                                           |
| Neon System Prompt     | `src/prompts/neon_prompt.ts`                                           | **New** — DB setup, API routes (Next.js), Data API patterns (React/Vite), auth guidance, security rules                                                                                      |
| Chat Stream Handlers   | `src/ipc/handlers/chat_stream_handlers.ts`                             | Modify — inject Neon prompt when `neonProjectId` present                                                                                                                                     |
| Response Processor     | `src/ipc/processors/response_processor.ts`                             | Modify — route `<dyad-execute-sql>` to Neon executor when Neon is active                                                                                                                     |
| Neon IPC Handlers      | `src/ipc/handlers/neon_handlers.ts`                                    | Modify — add `neon:list-projects`, `neon:set-app-project`, `neon:get-connection-uri`, `neon:execute-sql`                                                                                     |
| Neon IPC Types         | `src/ipc/types/neon.ts`                                                | Modify — add new contract schemas                                                                                                                                                            |
| App Details Page       | `src/pages/app-details.tsx`                                            | Modify — add NeonProjectSelector component                                                                                                                                                   |
| Neon Connector         | `src/components/NeonConnector.tsx`                                     | Refactor — use Card components, add per-app project/branch selection                                                                                                                         |
| Neon Integration       | `src/components/NeonIntegration.tsx`                                   | Enhance — show project info like SupabaseIntegration                                                                                                                                         |
| DB Schema              | `src/db/schema.ts`                                                     | Modify — add `neonActiveBranchId` column                                                                                                                                                     |

### Data Model Changes

Existing columns (already in `apps` table — no migration needed):

- `neonProjectId: string | null`
- `neonDevelopmentBranchId: string | null`
- `neonPreviewBranchId: string | null`

New column needed:

- `neonActiveBranchId: string | null` — tracks which branch the agent's SQL execution targets (defaults to development branch)

Agent context additions:

```typescript
interface AgentContext {
  // ... existing fields
  neonProjectId: string | null;
  neonDevelopmentBranchId: string | null;
  neonActiveBranchId: string | null;
  frameworkType: "nextjs" | "vite" | "other" | null; // Detected from appPath (e.g., check for next.config.*)
}
```

**Dependencies for generated Next.js apps:**

- `@neondatabase/auth` (Neon Auth server SDK for Next.js)
- `@neondatabase/neon-js` (Neon Auth client SDK — provides `auth`, `auth/react/ui`)
- `@neondatabase/serverless` (serverless Postgres driver)
- `drizzle-orm` + `drizzle-kit` (ORM and migrations)

**Dependencies for generated React/Vite apps:**

- `@neondatabase/neon-js` (unified client SDK — provides `createClient` for Data API queries, `BetterAuthReactAdapter` for auth, and `auth/react/ui` for pre-built components)
- `drizzle-orm` (ORM for type-safe queries, optional)

### API Changes

New IPC contracts in `src/ipc/types/neon.ts`:

| Contract                  | Input                             | Output                        |
| ------------------------- | --------------------------------- | ----------------------------- |
| `neon:list-projects`      | `{}`                              | `{ projects: NeonProject[] }` |
| `neon:set-app-project`    | `{ appId, projectId, branchIds }` | `{ success: boolean }`        |
| `neon:unset-app-project`  | `{ appId }`                       | `{ success: boolean }`        |
| `neon:execute-sql`        | `{ appId, query }`                | `{ result: string }`          |
| `neon:get-connection-uri` | `{ appId }`                       | `{ connectionUri: string }`   |
| `neon:get-table-schema`   | `{ appId, tableName? }`           | `{ schema: string }`          |
| `neon:set-active-branch`  | `{ appId, branchId }`             | `{ success: boolean }`        |

### Security Considerations

**Next.js apps: Connection string is a full-access credential**

Neon's `DATABASE_URL` connection string gives full read/write database access. The system prompt MUST:

1. **NEVER** place `DATABASE_URL` in client-side code
2. **NEVER** import `@neondatabase/serverless` in React components or browser code
3. **ONLY** use the connection string in:
   - Next.js API routes (`app/api/`)
   - Next.js Server Actions
   - Next.js Server Components
   - Environment variables (`.env.local`, not `.env`)

**React/Vite apps: Data API + RLS replaces server-side access**

React/Vite apps use the Neon Data API — a managed REST proxy that validates JWT tokens from Neon Auth and enforces PostgreSQL Row-Level Security (RLS). The system prompt MUST:

1. **NEVER** use `DATABASE_URL` or `@neondatabase/serverless` in React/Vite apps
2. **ALWAYS** use the Data API via `@neondatabase/neon-js` for database queries
3. **ALWAYS** configure RLS policies on all tables to enforce per-user access control
4. **ALWAYS** authenticate via Neon Auth before making Data API requests

This must be the FIRST rule in `neon_prompt.ts`, with the same prominence as RLS rules in `supabase_prompt.ts`.

## Example Code: Next.js Full Stack Integration

### Layer 1: Database Client (`src/db/index.ts`)

```typescript
import { neon } from "@neondatabase/serverless";
import { drizzle } from "drizzle-orm/neon-http";
import * as schema from "./schema";

const sql = neon(process.env.DATABASE_URL!);
export const db = drizzle(sql, { schema });
```

### Layer 1: Schema Definition (`src/db/schema.ts`)

```typescript
import { pgTable, uuid, text, timestamp, boolean } from "drizzle-orm/pg-core";

export const users = pgTable("users", {
  id: uuid("id").defaultRandom().primaryKey(),
  email: text("email").notNull().unique(),
  name: text("name"),
  createdAt: timestamp("created_at").defaultNow().notNull(),
});

export const todos = pgTable("todos", {
  id: uuid("id").defaultRandom().primaryKey(),
  title: text("title").notNull(),
  completed: boolean("completed").default(false).notNull(),
  userId: uuid("user_id")
    .references(() => users.id)
    .notNull(),
  createdAt: timestamp("created_at").defaultNow().notNull(),
});
```

### Layer 2: Next.js API Route (`app/api/todos/route.ts`)

```typescript
import { db } from "@/db";
import { todos } from "@/db/schema";
import { eq } from "drizzle-orm";
import { auth } from "@/lib/auth/server";
import { headers } from "next/headers";

export async function GET() {
  const session = await auth.getSession({ headers: await headers() });
  if (!session?.user?.id) {
    return Response.json({ error: "Unauthorized" }, { status: 401 });
  }

  const userTodos = await db
    .select()
    .from(todos)
    .where(eq(todos.userId, session.user.id));

  return Response.json(userTodos);
}

export async function POST(request: Request) {
  const session = await auth.getSession({ headers: await headers() });
  if (!session?.user?.id) {
    return Response.json({ error: "Unauthorized" }, { status: 401 });
  }

  const { title } = await request.json();
  const [newTodo] = await db
    .insert(todos)
    .values({ title, userId: session.user.id })
    .returning();

  return Response.json(newTodo, { status: 201 });
}
```

### Layer 3: Auth Server Configuration (`lib/auth/server.ts`)

Neon Auth is a managed auth service powered by Better Auth. Auth data is stored in your Neon database and branches automatically with database branches.

```typescript
import { createNeonAuth } from "@neondatabase/auth/next/server";

export const auth = createNeonAuth({
  baseUrl: process.env.NEON_AUTH_BASE_URL!,
  cookies: { secret: process.env.NEON_AUTH_COOKIE_SECRET! },
});
```

### Layer 3: Auth Route Handler (`app/api/auth/[...path]/route.ts`)

```typescript
import { auth } from "@/lib/auth/server";

export const { GET, POST } = auth.handler();
```

### Layer 3: Client-Side Auth (`src/lib/auth-client.ts`)

```typescript
import { createAuthClient } from "@neondatabase/neon-js/auth";

export const authClient = createAuthClient("/api/auth");
// Provides: useSession(), signIn.email(), signOut(), etc.
```

### Layer 3: Auth UI Components (optional)

Neon Auth provides pre-built UI components for sign-in/sign-up:

```tsx
import {
  NeonAuthUIProvider,
  AuthView,
} from "@neondatabase/neon-js/auth/react/ui";
import { authClient } from "@/lib/auth-client";

export function AuthPage() {
  return (
    <NeonAuthUIProvider authClient={authClient}>
      <AuthView pathname="sign-in" />
    </NeonAuthUIProvider>
  );
}
```

### Frontend: React Component (`src/components/TodoList.tsx`)

```typescript
"use client";

import { useEffect, useState } from "react";

interface Todo {
  id: string;
  title: string;
  completed: boolean;
}

export function TodoList() {
  const [todos, setTodos] = useState<Todo[]>([]);
  const [newTitle, setNewTitle] = useState("");

  useEffect(() => {
    fetch("/api/todos")
      .then((r) => r.json())
      .then(setTodos);
  }, []);

  async function addTodo(e: React.FormEvent) {
    e.preventDefault();
    const res = await fetch("/api/todos", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ title: newTitle }),
    });
    const todo = await res.json();
    setTodos([...todos, todo]);
    setNewTitle("");
  }

  return (
    <div>
      <form onSubmit={addTodo}>
        <input
          value={newTitle}
          onChange={(e) => setNewTitle(e.target.value)}
          placeholder="Add a todo..."
        />
        <button type="submit">Add</button>
      </form>
      <ul>
        {todos.map((todo) => (
          <li key={todo.id}>
            {todo.title} {todo.completed ? "(done)" : ""}
          </li>
        ))}
      </ul>
    </div>
  );
}
```

### Next.js Environment Variables (`.env.local`)

```bash
# Neon Database (injected by Dyad)
DATABASE_URL=postgresql://user:pass@ep-xxx.us-east-2.aws.neon.tech/dbname?sslmode=require

# Neon Auth (managed by Neon, values from Neon Console > Auth settings)
NEON_AUTH_BASE_URL=https://auth.neon.tech/...   # Auth service endpoint
NEON_AUTH_COOKIE_SECRET=your-cookie-secret-here  # Secret for session cookies
```

---

## Example Code: React/Vite Full Stack Integration

### Neon Client (`src/lib/auth.ts`)

A single client handles both auth and Data API queries. The `BetterAuthReactAdapter` integrates Neon Auth so JWT tokens are automatically included in Data API requests.

```typescript
import { createClient } from "@neondatabase/neon-js";
import { BetterAuthReactAdapter } from "@neondatabase/neon-js/auth/react/adapters";
import type { Database } from "../../types/database";

export const client = createClient<Database>({
  auth: {
    adapter: BetterAuthReactAdapter(),
    url: import.meta.env.VITE_NEON_AUTH_URL,
  },
  dataApi: {
    url: import.meta.env.VITE_NEON_DATA_API_URL,
  },
});
```

### RLS Policies (agent-generated SQL)

```sql
-- Enable RLS on the todos table
ALTER TABLE todos ENABLE ROW LEVEL SECURITY;

-- Users can only read their own todos
CREATE POLICY "crud-authenticated-policy-select"
  ON todos AS PERMISSIVE FOR SELECT TO "authenticated"
  USING ((select auth.user_id() = todos.user_id));

-- Users can only insert their own todos
CREATE POLICY "crud-authenticated-policy-insert"
  ON todos AS PERMISSIVE FOR INSERT TO "authenticated"
  WITH CHECK ((select auth.user_id() = todos.user_id));

-- Users can only update their own todos
CREATE POLICY "crud-authenticated-policy-update"
  ON todos AS PERMISSIVE FOR UPDATE TO "authenticated"
  USING ((select auth.user_id() = todos.user_id));

-- Users can only delete their own todos
CREATE POLICY "crud-authenticated-policy-delete"
  ON todos AS PERMISSIVE FOR DELETE TO "authenticated"
  USING ((select auth.user_id() = todos.user_id));
```

### Data API Queries (`src/components/TodoList.tsx`)

Uses the single `client` for both auth checks and Data API queries. Insert operations chain `.select().single()` to return the created record directly instead of re-fetching the full list.

```tsx
import { useState, useEffect } from "react";
import { client } from "@/lib/auth";

interface Todo {
  id: string;
  title: string;
  completed: boolean;
}

export function TodoList() {
  const [todos, setTodos] = useState<Todo[]>([]);
  const [newTitle, setNewTitle] = useState("");

  useEffect(() => {
    async function loadTodos() {
      const { data } = await client
        .from("todos")
        .select("id, title, completed, created_at")
        .order("created_at", { ascending: false });
      if (data) setTodos(data);
    }
    loadTodos();
  }, []);

  async function addTodo(e: React.FormEvent) {
    e.preventDefault();
    const { data, error } = await client
      .from("todos")
      .insert({ title: newTitle })
      .select("id, title, completed, created_at")
      .single();

    if (!error && data) {
      setTodos([data, ...todos]);
      setNewTitle("");
    }
  }

  async function deleteTodo(id: string) {
    const { error } = await client.from("todos").delete().eq("id", id);
    if (!error) {
      setTodos(todos.filter((t) => t.id !== id));
    }
  }

  return (
    <div>
      <form onSubmit={addTodo}>
        <input
          value={newTitle}
          onChange={(e) => setNewTitle(e.target.value)}
          placeholder="Add a todo..."
        />
        <button type="submit">Add</button>
      </form>
      <ul>
        {todos.map((todo) => (
          <li key={todo.id}>
            {todo.title} {todo.completed ? "(done)" : ""}
            <button onClick={() => deleteTodo(todo.id)}>Delete</button>
          </li>
        ))}
      </ul>
    </div>
  );
}
```

### Auth Page (`src/components/AuthPage.tsx`)

Auth is handled via the `BetterAuthReactAdapter` configured in the client. The adapter provides hooks and methods for sign-in/sign-up flows. Since auth is integrated into the single `client`, there's no separate auth client to import.

```tsx
import { useState } from "react";
import { client } from "@/lib/auth";

export function AuthPage({ onAuth }: { onAuth: () => void }) {
  const [email, setEmail] = useState("");
  const [password, setPassword] = useState("");
  const [isSignUp, setIsSignUp] = useState(true);

  async function handleSubmit(e: React.FormEvent) {
    e.preventDefault();
    const result = isSignUp
      ? await client.auth.signUp.email({
          name: email.split("@")[0] || "User",
          email,
          password,
        })
      : await client.auth.signIn.email({ email, password });

    if (result.error) {
      alert(result.error.message);
      return;
    }
    onAuth();
  }

  return (
    <form onSubmit={handleSubmit}>
      <h1>{isSignUp ? "Sign Up" : "Sign In"}</h1>
      <input
        type="email"
        placeholder="Email"
        value={email}
        onChange={(e) => setEmail(e.target.value)}
        required
      />
      <input
        type="password"
        placeholder="Password"
        value={password}
        onChange={(e) => setPassword(e.target.value)}
        required
      />
      <button type="submit">{isSignUp ? "Sign Up" : "Sign In"}</button>
      <p>
        <a
          href="#"
          onClick={(e) => {
            e.preventDefault();
            setIsSignUp(!isSignUp);
          }}
        >
          {isSignUp
            ? "Already have an account? Sign in"
            : "Need an account? Sign up"}
        </a>
      </p>
    </form>
  );
}
```

### React/Vite Environment Variables (`.env`)

```bash
# Neon Auth (managed by Neon, values from Neon Console > Auth settings)
VITE_NEON_AUTH_URL=https://ep-xxx.neonauth.us-east-2.aws.neon.build/neondb/auth

# Neon Data API (enabled in Neon Console > Data API)
VITE_NEON_DATA_API_URL=https://ep-xxx.data.us-east-2.aws.neon.build
```

### Neon Client Code Generated by Agent (for Dyad context)

**Next.js pattern:**

```typescript
// getNeonClientCode() for Next.js:
// "To connect to the Neon database, use this pattern:
//
// import { neon } from '@neondatabase/serverless';
// import { drizzle } from 'drizzle-orm/neon-http';
//
// const sql = neon(process.env.DATABASE_URL!);
// export const db = drizzle(sql);
//
// IMPORTANT: Only use this in server-side code (API routes, server actions, server components).
// NEVER import @neondatabase/serverless in client-side React components."
```

**React/Vite pattern:**

```typescript
// getNeonClientCode() for React/Vite:
// "To query the Neon database, use the Data API via @neondatabase/neon-js
// with BetterAuthReactAdapter for integrated auth:
//
// import { createClient } from '@neondatabase/neon-js';
// import { BetterAuthReactAdapter } from '@neondatabase/neon-js/auth/react/adapters';
// import type { Database } from '../../types/database';
//
// export const client = createClient<Database>({
//   auth: {
//     adapter: BetterAuthReactAdapter(),
//     url: import.meta.env.VITE_NEON_AUTH_URL,
//   },
//   dataApi: { url: import.meta.env.VITE_NEON_DATA_API_URL },
// });
//
// // Query example (PostgREST-compatible):
// const { data } = await client.from('todos').select('*').eq('completed', false);
//
// // Insert with select-back:
// const { data } = await client.from('todos').insert({ title }).select('*').single();
//
// IMPORTANT: Always configure RLS policies (TO "authenticated") on your tables.
// NEVER use DATABASE_URL or @neondatabase/serverless in React/Vite apps.
// The Data API validates JWT tokens from Neon Auth and enforces RLS automatically."
```

## Implementation Plan

### Phase 1: Agent Plumbing + UI Foundation (Small effort)

- [ ] Add `neonProjectId`, `neonDevelopmentBranchId`, `neonActiveBranchId`, `frameworkType` to `AgentContext` in `types.ts`
- [ ] Populate Neon context fields in `local_agent_handler.ts` from `chat.app.*`
- [ ] Populate `frameworkType` via framework detection (check for `next.config.*` in appPath, same pattern as `vercel_handlers.ts`)
- [ ] Add `"neon"` to `SUPPORTED_PROVIDERS` in `add_integration.ts` (Next.js and React/Vite)
- [ ] Add `neonActiveBranchId` column to `apps` table in `schema.ts`
- [ ] Move/refactor `NeonConnector` to app-details page using Card components (match SupabaseConnector pattern)
- [ ] Add `neon:list-projects` IPC handler (wire up API client's `listProjects`)
- [ ] Add `neon:set-app-project` and `neon:unset-app-project` IPC handlers
- [ ] Build NeonProjectSelector component with project dropdown + "Create New" option

### Phase 2: Neon Context + SQL Execution (Medium effort)

- [ ] Create `src/neon_admin/neon_context.ts` with:
  - `executeNeonSql()` — uses `@neondatabase/serverless` (extract from `neon_timestamp_utils.ts`)
  - `getNeonProjectInfo()` — project ID, branches, table names via `information_schema`
  - `getNeonTableSchema()` — columns, constraints, indexes via `information_schema`
  - `getNeonClientCode(frameworkType)` — generates framework-specific boilerplate: Drizzle + `@neondatabase/serverless` for Next.js, `createClient` + Data API for React/Vite
  - `getNeonContext()` — full context for agent prompt
- [ ] Create `get_neon_project_info.ts` agent tool (mirrors `get_supabase_project_info.ts`)
- [ ] Create `get_neon_table_schema.ts` agent tool (mirrors `get_supabase_table_schema.ts`)
- [ ] Create `execute_neon_sql.ts` agent tool (mirrors `execute_sql.ts`, uses serverless driver)
- [ ] Register all 3 tools in `tool_definitions.ts`
- [ ] Add `neon:execute-sql`, `neon:get-connection-uri`, `neon:get-table-schema` IPC contracts
- [ ] Update `response_processor.ts` to route `<dyad-execute-sql>` to Neon executor when `neonProjectId` is set
- [ ] Add `neon:set-active-branch` IPC handler for branch switching

### Phase 3: System Prompt + Branch UI (Medium effort)

- [ ] Write `src/prompts/neon_prompt.ts` with framework-conditional sections:
  - **Shared**: Auth recommendation (Neon Auth), RLS policy templates, empty database first-run guidance, migration patterns
  - **Next.js**: Connection security rules (NEVER client-side `DATABASE_URL`), Drizzle ORM setup, API route / Server Action patterns, `@neondatabase/auth` server SDK + `@neondatabase/neon-js` client SDK
  - **React/Vite**: Data API setup (`createClient<Database>` with `BetterAuthReactAdapter` + auth/dataApi URLs), PostgREST-style query patterns (`.from().select().eq()`, `.insert().select().single()`), RLS policies with `TO "authenticated"` role required on all tables, `@neondatabase/neon-js` client SDK only (no server SDK), NEVER use `DATABASE_URL` or `@neondatabase/serverless`
- [ ] Inject the correct prompt section based on `frameworkType` from `AgentContext`
- [ ] Write `NEON_NOT_AVAILABLE_SYSTEM_PROMPT` (parallel to Supabase's)
- [ ] Integrate Neon prompt into `chat_stream_handlers.ts` (conditional on `neonProjectId`)
- [ ] Build branch selector UI in integration card with color-coded badges
- [ ] Wire branch selection to `neonActiveBranchId` on app record
- [ ] Ensure branch change updates connection URI for SQL execution
- [ ] Add Neon-specific error messages in response processor (auth failure vs query failure)

### Phase 4: Polish + Testing (Small-Medium effort)

- [ ] Enhance `NeonIntegration.tsx` settings page (show project info, usage)
- [ ] Handle Portal template migration (detect existing `neonProjectId`, show connected state)
- [ ] Unit tests for `neon_context.ts` functions with mocked `@neondatabase/serverless`
- [ ] Agent tool tests (enable/disable based on context)
- [ ] E2E tests: connect flow, project selection, SQL execution, schema introspection
- [ ] System prompt tests: verify Neon instructions injected correctly
- [ ] Integration test: full flow from connect → schema → SQL → code generation

## Testing Strategy

- [ ] **Unit**: `neon_context.ts` functions with mocked `@neondatabase/serverless` and `@neondatabase/api-client`
- [ ] **Agent tools**: Mock `AgentContext` with `neonProjectId` set/unset, verify tools enable/disable correctly
- [ ] **E2E (connect flow)**: Extend existing `neon:fake-connect` fixture for Next.js and React/Vite templates
- [ ] **E2E (SQL execution)**: Agent generates schema, executes `<dyad-execute-sql>`, verifies result
- [ ] **E2E (schema introspection)**: After table creation, verify `get_neon_table_schema` returns correct columns
- [ ] **System prompt**: Verify Neon instructions injected when `neonProjectId` present, NOT when Supabase connected
- [ ] **Security (Next.js)**: Verify system prompt prevents client-side `DATABASE_URL` usage in generated code
- [ ] **Security (React/Vite)**: Verify system prompt enforces Data API + RLS pattern, never uses `DATABASE_URL` or `@neondatabase/serverless`
- [ ] **Branch switching**: Verify SQL execution targets correct branch after branch change

## Risks & Mitigations

| Risk                                           | Likelihood | Impact | Mitigation                                                                                                                                                                                 |
| ---------------------------------------------- | ---------- | ------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| Connection string exposed in client code       | Medium     | High   | Next.js: system prompt rule #1 NEVER client-side. React/Vite: uses Data API instead, no connection string needed.                                                                          |
| AI generates insecure homegrown auth           | Medium     | High   | Prompt explicitly forbids JWT+bcrypt, recommends Neon Auth (Better Auth) only. Auth data lives in Neon DB.                                                                                 |
| Neon free tier quota exhaustion                | Low        | Medium | Document in system prompt. Consider surfacing usage in `get_neon_project_info`.                                                                                                            |
| Branch switching causes data confusion         | Low        | Medium | Toast notification on branch change. Agent prompt mentions active branch.                                                                                                                  |
| Portal template users lose existing connection | Low        | High   | Detect existing `neonProjectId` in new flow, show connected state.                                                                                                                         |
| On-demand connection URI fetch latency         | Medium     | Low    | Acceptable for v1 (~200-500ms). Cache in v2 if needed.                                                                                                                                     |
| Missing RLS policies in React/Vite apps        | Medium     | High   | Data API exposes full table without RLS. System prompt must generate RLS policies for every table. Agent should run `ALTER TABLE ... ENABLE ROW LEVEL SECURITY` as part of table creation. |
| Data API not enabled on Neon project           | Medium     | Medium | Agent checks Data API status via `get_neon_project_info`. System prompt guides user to enable it in Neon Console.                                                                          |

## Open Questions

1. **Neon free tier limits**: Should `get_neon_project_info` surface current usage (storage, compute hours)? This would help users and the agent avoid heavy operations that burn through quotas.

2. **NeonConfigure preview panel**: The existing `NeonConfigure.tsx` shows branch visualization in the preview panel for Portal apps. Should this be removed/replaced by the new branch selector in the integration card, or kept as a read-only status display?

3. **Mutual exclusivity enforcement**: When a user has Supabase connected and tries to connect Neon (or vice versa), should we show a warning dialog, or silently hide the other provider's connect button?

## Decision Log

| Decision                                 | Reasoning                                                                                                                                                                            | Alternatives Considered                                                                              |
| ---------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------- |
| Next.js and React/Vite in v1             | Next.js uses DATABASE_URL server-side. React/Vite uses Neon Data API (managed REST proxy with JWT validation + RLS), eliminating the need for a server layer or exposed credentials. | Next.js only (unnecessarily restrictive now that Data API exists)                                    |
| Recommend Neon Auth                      | Neon now provides built-in auth via Better Auth. Auth data stored in the Neon database, branches with database. No external auth providers needed.                                   | NextAuth.js (separate config + secrets); Clerk (external SaaS); homegrown JWT+bcrypt (security risk) |
| Full branch selector in v1               | Branching is Neon's key differentiator over Supabase. Color-coded badges provide clear visual hierarchy.                                                                             | Default to dev only (simpler); Read-only display (compromise)                                        |
| Mutually exclusive providers per app     | Agent prompt can't cleanly handle both Supabase and Neon contexts. Avoids ambiguity in SQL execution target.                                                                         | Allow both (complex, no clear user value)                                                            |
| SQL execution in Dyad (management plane) | Matches Supabase pattern. Agent needs to create tables and seed data during build. Credentials already stored.                                                                       | Only in generated app (limits agent capabilities)                                                    |
| Fetch connection URI on-demand           | Consistent with existing `neon_timestamp_utils.ts` pattern. Avoids credential rotation complexity.                                                                                   | Cache (faster but more complex)                                                                      |
| `@neondatabase/serverless` for SQL       | Already a dependency. HTTP-based, works in Electron. Lower latency than Management API for queries.                                                                                  | Management API SQL endpoint (higher latency, fewer features)                                         |

---

_Generated by dyad:swarm-to-plan_
