# Phase 2: Admin Area & Interactive Features - Research

**Researched:** 2026-05-15
**Domain:** Next.js authentication, admin CRUD, form handling, route protection
**Confidence:** MEDIUM-HIGH

## Summary

Phase 2 requires implementing Next.js 16 authentication for admin area (`/admin`), form-based CRUD operations for clients/phases/tasks/deliverables/payments, and client-facing interactive features (approval, comments). The research reveals a critical version decision: **next-auth v4 has unresolved peer dependency issues with Next.js 16**, requiring workarounds (`--legacy-peer-deps` flag). The CONTEXT.md decision to use "next-auth@4" is technically viable but carries technical debt. The phase architecture is sound: Edge middleware protection (proxy.ts) for admin routes, Server Actions for mutations, and API routes for client interactions (separate auth path). Key dependencies are not yet installed.

**Primary recommendation:** Proceed with next-auth v4 as locked in CONTEXT.md, but document the workaround requirement. All other patterns (Server Actions, revalidatePath, Zod+RHF forms, tabs) are stable and production-ready on Next.js 16.

## User Constraints (from CONTEXT.md)

### Locked Decisions

**D-01: Auth.js v4 (`next-auth@4`)** — versione stabile. Installare `next-auth@4` + credentials via env vars (ADMIN_EMAIL + ADMIN_PASSWORD). No DB table per utenti.

**D-02: Credenziali admin via env vars** — Singolo admin hardcoded in `.env.local`. CredentialsProvider di Auth.js valida contro le env vars.

**D-03: Sessione JWT** — Nessuna tabella session nel DB. JWT stateless firmato, scadenza 30 giorni.

**D-04: Protezione route admin** — `src/proxy.ts` gestisce `/c/[token]`. Aggiungere matcher `/admin/:path*` → redirect a `/admin/login` se no session. Check sessione nel middleware via `getToken` da next-auth/jwt.

**D-05: Server Actions** — Next.js 15/16 nativo. Nessuna API route separata per CRUD admin. Form → Server Action → Drizzle → `revalidatePath`.

**D-06: API route solo per client interactions** — Approvazioni e commenti del cliente su API routes (non Server Actions). Route pattern: `POST /api/client/approve` e `POST /api/client/comment`. Validate token via header/cookie, non Auth.js.

**D-07: Lista → dettaglio** — `/admin` → lista clienti con badge stato pagamenti. Click → `/admin/clients/[id]` dettaglio completo.

**D-08: Tabs per sezioni cliente** — Nella pagina dettaglio cliente, tabs: Panoramica | Fasi & Task | Documenti | Pagamenti | Commenti. Aggiungere `@radix-ui/react-tabs`.

**D-09: Sidebar assente in v1** — Nessuna sidebar globale. Nav minimal: logo + link "Clienti" + pulsante logout.

**D-10: Approvazione deliverable** — Pulsante "Approva" visibile solo se `approved_at` è null. POST a `/api/client/approve` con `{ deliverableId }`.

**D-11: Commenti inline** — Textarea + pulsante "Invia" sotto task/deliverable. POST a `/api/client/comment`. Lista commenti sopra il form, ordinati per `created_at` asc.

### Claude's Discretion

- Struttura esatta delle Server Actions (file naming)
- Ordine campi nei form admin
- Icone lucide-react per stati
- Colori badge admin

### Deferred Ideas (OUT OF SCOPE)

- Brand customization panel (Phase 3+)
- Sidebar globale (v1 non necessaria)
- Real-time commenti (WebSocket/polling → v2)
- Multi-admin / ruoli
- Password change UI

## Phase Requirements

| ID | Description | Research Support |
|----|-------------|------------------|
| ADMIN-01 | Vista di tutti i clienti con stato sintetico | Server Component + Drizzle query per lista con badge pagamenti |
| ADMIN-02 | Gestione completa di ogni cliente: fasi, task, documenti, pagamenti | Server Actions per CRUD su tutte le tabelle; proxy.ts protezione route `/admin/[*]`; revalidatePath aggiorna UI |
| DASH-05 | Il cliente può approvare i deliverable dalla sua area | API route `POST /api/client/approve` — token validation — UPDATE `approved_at` immutabile |
| DASH-06 | Il cliente può lasciare commenti su task e deliverable | API route `POST /api/client/comment` — INSERT comments table — lista commenti con author (client/admin) |

## Architectural Responsibility Map

| Capability | Primary Tier | Secondary Tier | Rationale |
|------------|-------------|----------------|-----------|
| Admin authentication & session | API / Backend (proxy.ts) | Frontend Server (layout redirect) | JWT validation happens at Edge; session unavailable → redirect to login before rendering |
| Admin CRUD mutations | API / Backend (Server Actions) | Frontend Server (forms) | Server Actions run in Node context; Drizzle mutations; revalidatePath triggers UI refresh |
| Admin UI rendering | Frontend Server (Server Components) | Browser (Client Components for interactivity) | List/detail pages are Server Components with data from Drizzle; tabs/forms use Client Components for state |
| Client approval/comments | API / Backend (route handlers) | Browser (client tokens in URL/cookie) | Separate auth path from admin; token validation in route handlers; mutations persist to DB |
| Client-facing UI (approval, comments) | Browser / Client | Frontend Server (token validation via middleware) | Approval button, comment form are interactive client components; middleware validates token before rendering |
| Payment status display | API / Backend (Drizzle query) | Frontend Server (Server Component) | Payments table joins with clients; status badge computed server-side; client API returns `accepted_total` only |

## Standard Stack

### Core Authentication & Middleware

| Library | Version | Purpose | Why Standard |
|---------|---------|---------|--------------|
| next-auth | 4.24.14 | JWT-based admin session, credentials provider | Industry standard for Next.js; stateless JWT avoids DB session lookups at Edge [VERIFIED: npm registry] |
| next-auth/jwt | bundled with next-auth | Token signing/verification in middleware | Required for getToken() calls in proxy.ts [VERIFIED: next-auth docs] |

### Form Handling & Validation

| Library | Version | Purpose | When to Use |
|---------|---------|---------|-------------|
| react-hook-form | 7.75.0 | Client-side form state + server submission | Already installed; lightweight; pairs with Zod for validation [VERIFIED: package.json] |
| @hookform/resolvers | 5.2.2 | Adapter for Zod → RHF integration | Already installed; official resolver [VERIFIED: package.json] |
| zod | 4.4.3 | Schema validation (client & server) | Already installed; enables validation reuse across RHF forms and Server Actions [VERIFIED: package.json] |

### UI Components

| Library | Version | Purpose | When to Use |
|---------|---------|---------|-------------|
| @radix-ui/react-tabs | 1.0.x | Tabs primitive (Radix-based) | Not yet installed; needed for admin detail page tabs per D-08 [CITED: shadcn/ui docs] |
| @radix-ui/react-dialog | 1.1.x | Modal/dialog primitive | Optional; can defer if confirm modals not needed in Wave 1 |
| shadcn/ui (tabs component) | — | Pre-configured Tabs built on Radix | Install via `npx shadcn@latest add tabs` after @radix-ui/react-tabs added [CITED: shadcn/ui] |

### Existing, Reusable Components (from Phase 1)

| Library | Version | Purpose | Status |
|---------|---------|---------|--------|
| next | 16.2.6 | App Router, Server Components, Server Actions | ✅ Installed, using in Phase 2 |
| drizzle-orm | 0.45.2 | Type-safe ORM + postgres-js connection | ✅ Installed, patterns established Phase 1 |
| postgres | 3.4.9 | Postgres client (Node.js only, not Edge-compatible) | ✅ Installed; used in API routes (not middleware) |
| nanoid | 5.1.11 | Cryptographic token generation | ✅ Installed; schema uses for `token` field |
| tailwindcss | 4.x | Styling | ✅ Installed v4 in Phase 1 |
| shadcn/ui (installed) | — | badge, button, card, input, label, select, table, textarea | ✅ All used in Phase 1; reuse for admin UI |
| lucide-react | 1.14.0 | Icons | ✅ Installed Phase 1; use for payment status icons |

### Installation

**New packages to add:**
```bash
npm install next-auth@4.24.14 @radix-ui/react-tabs
npx shadcn@latest add tabs
```

**Note:** next-auth v4 with Next.js 16 requires `--legacy-peer-deps` flag due to unresolved peer dependency issue:
```bash
npm install next-auth@4.24.14 --legacy-peer-deps
```

**Version verification:**
```bash
npm view next-auth@4.24.14 version      # Should show: 4.24.14, published ~May 2026
npm view @radix-ui/react-tabs version   # Should show: 1.x.x (latest stable)
```

## Architecture Patterns

### System Architecture Diagram

```
┌─────────────────────────────────────────────────────────────┐
│  BROWSER / CLIENT                                           │
│  ┌──────────────────┐  ┌──────────────────┐               │
│  │ Admin Form       │  │ Client Dashboard │               │
│  │ (RHF + Zod)      │  │ (Approval/Comment)               │
│  └────────┬─────────┘  └────────┬─────────┘               │
└───────────┼──────────────────────┼───────────────────────────┘
            │                      │
            │ POST /api/[...] +    │ POST /api/client/[...] +
            │ Server Action        │ client token
            │                      │
┌───────────▼──────────────────────▼───────────────────────────┐
│  FRONTEND SERVER (Next.js App Router)                       │
│  ┌──────────────────────────────────────────────────────┐   │
│  │ Route Handler: POST /api/client/approve              │   │
│  │ Route Handler: POST /api/client/comment              │   │
│  │ Server Actions: createClient, updatePhase, etc.      │   │
│  └──────────────────────────────────────────────────────┘   │
└───────────┬──────────────────────────────────────────────────┘
            │
            │ Token validation,
            │ Drizzle mutation
            │
┌───────────▼──────────────────────────────────────────────────┐
│  DATABASE (Hetzner Postgres)                                │
│  ┌──────────────────────────────────────────────────────┐   │
│  │ clients, phases, tasks, deliverables, comments,     │   │
│  │ payments, documents, notes, service_catalog,        │   │
│  │ quote_items                                         │   │
│  └──────────────────────────────────────────────────────┘   │
└───────────────────────────────────────────────────────────────┘

SEPARATELY: Edge Layer (proxy.ts)
┌─────────────────────────────────────────────────────────────┐
│  EDGE (Vercel / Next.js middleware layer)                   │
│  ┌──────────────────────────────────────────────────────┐   │
│  │ proxy.ts: getToken() → session exists?               │   │
│  │ → Route: /admin/[*] → getToken ok? → allow : 404     │   │
│  │ → Route: /c/[token]/[*] → validateToken ok? → allow  │   │
│  └──────────────────────────────────────────────────────┘   │
└─────────────────────────────────────────────────────────────┘
```

### Route Structure & Protection

**Admin Routes (Protected by Edge middleware):**
- `/admin/login` — GET form, POST credentials (unprotected)
- `/admin` — GET list of clients (protected, Server Component)
- `/admin/clients/[id]` — GET detail + CRUD forms (protected, Server Component + Client Components)
- `/admin/clients/[id]/phases` — Nested detail (protected)
- `/admin/clients/[id]/payments` — Nested detail (protected)

**Client Routes (Already protected in Phase 1):**
- `/c/[token]/` — Client dashboard (token validation via proxy.ts)
- `/c/[token]/task/[taskId]` — Task detail with comments

**API Routes (Internal, not middleware-protected at Edge):**
- `POST /api/client/approve` — Expects client token in body/header
- `POST /api/client/comment` — Expects client token in body/header
- `POST /api/internal/validate-token` — Called by proxy.ts (no auth needed, internal only)

### Recommended Project Structure

```
src/
├── app/
│   ├── admin/
│   │   ├── login/
│   │   │   ├── page.tsx          # Login form
│   │   │   └── actions.ts        # signIn Server Action
│   │   ├── clients/
│   │   │   ├── page.tsx          # List all clients
│   │   │   ├── actions.ts        # createClient, deleteClient
│   │   │   └── [id]/
│   │   │       ├── page.tsx      # Detail page with tabs
│   │   │       ├── actions.ts    # updateClient, createPhase, etc.
│   │   │       └── layout.tsx    # Detail layout (optional nested)
│   │   ├── layout.tsx            # Admin layout (nav, logout)
│   │   └── components/
│   │       ├── ClientForm.tsx
│   │       ├── PhaseForm.tsx
│   │       └── PaymentBadge.tsx
│   ├── c/
│   │   └── [token]/
│   │       ├── components/
│   │       │   ├── ApprovalButton.tsx
│   │       │   └── CommentForm.tsx
│   │       └── ...                # Phase 1 routes (reuse)
│   └── api/
│       ├── client/
│       │   ├── approve/
│       │   │   └── route.ts      # POST /api/client/approve
│       │   └── comment/
│       │       └── route.ts      # POST /api/client/comment
│       └── internal/
│           └── validate-token/
│               └── route.ts      # (existing, Phase 1)
├── auth.ts                       # next-auth config (NEW)
├── proxy.ts                      # Edge middleware (MODIFY)
├── db/
│   └── schema.ts                 # (unchanged, Phase 1)
└── lib/
    └── client-view.ts            # (unchanged, Phase 1)
```

### Pattern 1: Server Actions for Admin CRUD

**What:** Async functions marked with `'use server'` that run on the server, handle form submissions, mutate the database via Drizzle, and revalidate the cache to refresh the UI.

**When to use:** Every admin form (create/edit/delete client, phase, task, payment). Never call from client-side mutation code directly.

**Example:**

```typescript
// src/app/admin/clients/actions.ts
'use server'

import { db } from '@/db'
import { clients, phases } from '@/db/schema'
import { nanoid } from 'nanoid'
import { revalidatePath } from 'next/cache'
import { createClientSchema } from '@/lib/validation'

export async function createClient(formData: FormData) {
  const data = Object.fromEntries(formData)
  const validated = createClientSchema.parse(data)
  
  const token = nanoid() // Generate secret link token
  
  const newClient = await db.insert(clients).values({
    name: validated.name,
    brand_name: validated.brand_name,
    brief: validated.brief,
    token: token,
    accepted_total: '0',
  }).returning()
  
  revalidatePath('/admin') // Refresh client list
  return { success: true, clientId: newClient[0].id, token }
}

// src/app/admin/clients/page.tsx (Server Component)
import { db } from '@/db'
import { clients } from '@/db/schema'
import { ClientForm } from './components/ClientForm'

export default async function AdminClientsPage() {
  const allClients = await db.query.clients.findMany()
  
  return (
    <div>
      <h1>All Clients</h1>
      <ClientForm onSubmit={createClient} />
      <ClientList clients={allClients} />
    </div>
  )
}

// src/app/admin/clients/components/ClientForm.tsx (Client Component)
'use client'

import { useFormState, useFormStatus } from 'react-dom'
import { createClient } from '../actions'

export function ClientForm() {
  const [state, formAction] = useFormState(createClient, null)
  
  return (
    <form action={formAction}>
      <input name="name" required />
      <input name="brand_name" required />
      <textarea name="brief" required />
      <SubmitButton />
      {state?.success && <p>Client created! Token: {state.token}</p>}
    </form>
  )
}

function SubmitButton() {
  const { pending } = useFormStatus()
  return <button disabled={pending}>{pending ? 'Creating...' : 'Create Client'}</button>
}
```

[Source: https://nextjs.org/docs/13/app/building-your-application/data-fetching/server-actions-and-mutations]

### Pattern 2: Admin Route Protection via proxy.ts + getToken

**What:** Edge middleware that checks JWT session token before rendering `/admin` routes.

**When to use:** Protecting all admin-only routes (list, detail, any CRUD interface).

**Example:**

```typescript
// src/proxy.ts (MODIFY existing file)
import { NextRequest, NextResponse } from 'next/server'
import { getToken } from 'next-auth/jwt'

const ADMIN_MATCHER = /^\/admin/
const CLIENT_MATCHER = /^\/c\/[a-zA-Z0-9_-]+/

export async function proxy(request: NextRequest) {
  const pathname = request.nextUrl.pathname

  // Admin route protection
  if (ADMIN_MATCHER.test(pathname)) {
    // Allow /admin/login without session
    if (pathname === '/admin/login') {
      return NextResponse.next()
    }

    const token = await getToken({
      req: request,
      secret: process.env.NEXTAUTH_SECRET,
    })

    if (!token) {
      // Redirect to login if no session
      return NextResponse.redirect(new URL('/admin/login', request.url))
    }

    return NextResponse.next()
  }

  // Client token validation (Phase 1 pattern — unchanged)
  const tokenMatch = pathname.match(/^\/c\/([a-zA-Z0-9_-]+)/)
  if (tokenMatch) {
    const token = tokenMatch[1]
    try {
      const validateUrl = new URL(
        `/api/internal/validate-token?token=${encodeURIComponent(token)}`,
        request.url
      )
      const res = await fetch(validateUrl.toString())
      if (!res.ok) {
        return NextResponse.rewrite(new URL('/not-found', request.url))
      }
      return NextResponse.next()
    } catch {
      return NextResponse.rewrite(new URL('/not-found', request.url))
    }
  }

  return NextResponse.rewrite(new URL('/not-found', request.url))
}

export const config = {
  matcher: ['/admin/:path*', '/c/:path*'],
}
```

[Source: https://authjs.dev/getting-started/session-management/protecting]

### Pattern 3: next-auth v4 Configuration with Credentials

**What:** One-file auth config exporting NextAuth handlers for `/api/auth/[...nextauth]` route. Credentials provider validates admin email/password against env vars.

**When to use:** Initialize once at `src/auth.ts`. Used by all routes via `getSession()` (server) or `useSession()` (client).

**Example:**

```typescript
// src/auth.ts (NEW)
import NextAuth from 'next-auth'
import CredentialsProvider from 'next-auth/providers/credentials'

export const { handlers, auth } = NextAuth({
  providers: [
    CredentialsProvider({
      credentials: {
        email: { label: 'Email', type: 'text' },
        password: { label: 'Password', type: 'password' },
      },
      async authorize(credentials) {
        // Validate against env vars only — no DB lookup
        if (
          credentials?.email === process.env.ADMIN_EMAIL &&
          credentials?.password === process.env.ADMIN_PASSWORD
        ) {
          return {
            id: 'admin',
            email: process.env.ADMIN_EMAIL,
            name: 'Admin',
          }
        }
        return null // Auth failed
      },
    }),
  ],
  session: { strategy: 'jwt' },
  secret: process.env.NEXTAUTH_SECRET,
  pages: { signIn: '/admin/login' },
})

// src/app/api/auth/[...nextauth]/route.ts (NEW)
import { handlers } from '@/auth'
export const { GET, POST } = handlers
```

[Source: https://next-auth.js.org/providers/credentials]

### Pattern 4: Form Validation with Zod + React Hook Form

**What:** Define a Zod schema, pass to RHF via `zodResolver`, handle both client-side and server-side validation.

**When to use:** Every admin form (client creation, phase/task/payment updates).

**Example:**

```typescript
// src/lib/validation.ts (NEW)
import { z } from 'zod'

export const createClientSchema = z.object({
  name: z.string().min(1, 'Client name required'),
  brand_name: z.string().min(1, 'Brand name required'),
  brief: z.string().min(10, 'Brief must be at least 10 characters'),
})

export type CreateClientInput = z.infer<typeof createClientSchema>

// src/app/admin/clients/components/ClientForm.tsx (Client Component)
'use client'

import { useForm } from 'react-hook-form'
import { zodResolver } from '@hookform/resolvers/zod'
import { createClientSchema, type CreateClientInput } from '@/lib/validation'
import { createClient } from '../actions'

export function ClientForm() {
  const { register, handleSubmit, formState: { errors } } = useForm<CreateClientInput>({
    resolver: zodResolver(createClientSchema),
  })

  async function onSubmit(data: CreateClientInput) {
    const formData = new FormData()
    Object.entries(data).forEach(([k, v]) => formData.append(k, v))
    await createClient(formData)
  }

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <div>
        <label>Client Name</label>
        <input {...register('name')} />
        {errors.name && <span>{errors.name.message}</span>}
      </div>
      <div>
        <label>Brand Name</label>
        <input {...register('brand_name')} />
        {errors.brand_name && <span>{errors.brand_name.message}</span>}
      </div>
      <div>
        <label>Project Brief</label>
        <textarea {...register('brief')} />
        {errors.brief && <span>{errors.brief.message}</span>}
      </div>
      <button type="submit">Create Client</button>
    </form>
  )
}
```

[Source: https://medium.com/@ctrlaltmonique/how-to-use-react-hook-form-zod-with-next-js-server-actions-437aaca3d72d]

### Pattern 5: Client API Routes (Token-Based, Separate from Admin)

**What:** Route handlers that validate client token (from request body or cookie) before allowing approval/comment mutations.

**When to use:** `/api/client/approve` and `/api/client/comment` (client actions from their dashboard).

**Example:**

```typescript
// src/app/api/client/approve/route.ts (NEW)
import { NextRequest, NextResponse } from 'next/server'
import { db } from '@/db'
import { deliverables } from '@/db/schema'
import { eq } from 'drizzle-orm'

export async function POST(request: NextRequest) {
  const { deliverableId, clientToken } = await request.json()

  // Validate client token via DB lookup
  const client = await db.query.clients.findFirst({
    where: (c, { eq }) => eq(c.token, clientToken),
  })

  if (!client) {
    return NextResponse.json({ error: 'Invalid token' }, { status: 401 })
  }

  // Verify deliverable belongs to this client (via task → phase → client)
  const deliverable = await db.query.deliverables.findFirst({
    where: (d, { eq }) => eq(d.id, deliverableId),
    with: {
      task: {
        with: {
          phase: true,
        },
      },
    },
  })

  if (!deliverable || deliverable.task.phase.client_id !== client.id) {
    return NextResponse.json({ error: 'Unauthorized' }, { status: 403 })
  }

  if (deliverable.approved_at) {
    return NextResponse.json({ error: 'Already approved' }, { status: 400 })
  }

  // Update approved_at — IMMUTABLE per D-03 (CLAUDE.md)
  await db
    .update(deliverables)
    .set({ approved_at: new Date(), status: 'approved' })
    .where(eq(deliverables.id, deliverableId))

  return NextResponse.json({ success: true })
}

// src/app/c/[token]/components/ApprovalButton.tsx (Client Component)
'use client'

import { useState } from 'react'
import { Button } from '@/components/ui/button'

export function ApprovalButton({
  deliverableId,
  clientToken,
  isApproved,
}: {
  deliverableId: string
  clientToken: string
  isApproved: boolean
}) {
  const [loading, setLoading] = useState(false)

  async function handleApprove() {
    setLoading(true)
    const res = await fetch('/api/client/approve', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ deliverableId, clientToken }),
    })

    if (res.ok) {
      // Page refresh to update state — no real-time in v1
      window.location.reload()
    } else {
      alert('Failed to approve')
    }
    setLoading(false)
  }

  if (isApproved) {
    return <span className="text-green-600">✓ Approved</span>
  }

  return (
    <Button onClick={handleApprove} disabled={loading}>
      {loading ? 'Approving...' : 'Approve'}
    </Button>
  )
}
```

[Source: CONTEXT.md D-06, D-10]

### Pattern 6: Admin Tabs (Radix-based via shadcn/ui)

**What:** Tab component from shadcn/ui (Radix Tabs underneath) for organizing admin detail page sections (Overview | Phases & Tasks | Documents | Payments | Comments).

**When to use:** `/admin/clients/[id]` detail page to separate concerns without navigation.

**Example:**

```typescript
// src/app/admin/clients/[id]/page.tsx (Server Component)
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'
import { ClientOverview } from './components/ClientOverview'
import { PhasesPanel } from './components/PhasesPanel'
import { DocumentsPanel } from './components/DocumentsPanel'
import { PaymentsPanel } from './components/PaymentsPanel'
import { CommentsPanel } from './components/CommentsPanel'

export default async function ClientDetailPage({ params: { id } }) {
  const client = await db.query.clients.findFirst({
    where: (c, { eq }) => eq(c.id, id),
    with: { phases: true, payments: true, documents: true, notes: true },
  })

  return (
    <div>
      <h1>{client?.name}</h1>
      <Tabs defaultValue="overview">
        <TabsList>
          <TabsTrigger value="overview">Overview</TabsTrigger>
          <TabsTrigger value="phases">Phases & Tasks</TabsTrigger>
          <TabsTrigger value="documents">Documents</TabsTrigger>
          <TabsTrigger value="payments">Payments</TabsTrigger>
          <TabsTrigger value="comments">Comments</TabsTrigger>
        </TabsList>

        <TabsContent value="overview">
          <ClientOverview client={client} />
        </TabsContent>

        <TabsContent value="phases">
          <PhasesPanel clientId={id} phases={client?.phases || []} />
        </TabsContent>

        <TabsContent value="documents">
          <DocumentsPanel documents={client?.documents || []} />
        </TabsContent>

        <TabsContent value="payments">
          <PaymentsPanel payments={client?.payments || []} />
        </TabsContent>

        <TabsContent value="comments">
          <CommentsPanel comments={client?.notes || []} />
        </TabsContent>
      </Tabs>
    </div>
  )
}
```

[Source: https://ui.shadcn.com/docs/components/radix/tabs]

### Anti-Patterns to Avoid

- **Don't use useEffect to fetch data inside admin components.** Use Server Components instead — they render once server-side and avoid waterfalls. [CITED: Phase 1 pattern]
- **Don't store session in state.** Use `getToken()` in proxy.ts (Edge) and `getSession()` in Server Components. Avoid client-side session stores which are slower and less secure.
- **Don't hand-roll JWT signing.** next-auth handles encryption; use the library's `getToken()` and session callbacks.
- **Don't expose `quote_items` in any admin API.** Client API must return only `accepted_total` field. Enforced at query layer, not UI. [CITED: CLAUDE.md constraint #2]
- **Don't allow modification of `approved_at` once set.** Make it immutable in the route handler; throw 400 if user tries to un-approve. [CITED: CLAUDE.md constraint #3]
- **Don't call postgres-js directly in proxy.ts.** postgres-js requires Node.js `net`/`tls` modules unavailable at Edge. Always use `fetch()` to internal API routes. [CITED: Phase 1 proxy.ts comment]

## Don't Hand-Roll

| Problem | Don't Build | Use Instead | Why |
|---------|-------------|-------------|-----|
| Admin session management | Custom JWT + cookie library | next-auth v4 CredentialsProvider | Built-in refresh handling, CSRF protection, type-safe session object |
| Form validation | Custom validation functions | Zod + @hookform/resolvers | Reuse same schema client-side (RHF) and server-side (Server Actions). Errors map automatically. |
| Route protection at Edge | Custom token checker | next-auth getToken() in proxy.ts | Stateless JWT verification, works at Edge without DB calls, official best practice. |
| Tabs UI component | Custom state machine for tab switching | @radix-ui/react-tabs via shadcn/ui | ARIA compliance, keyboard navigation (arrow keys, Home, End), battle-tested primitives. |
| Client approval workflow | Custom approval + state flags | `deliverables.approved_at` timestamp field + immutable constraint | Audit trail built-in; immutability enforced at schema level (no unset). Single UPDATE mutation. |
| Comment threading | Hand-rolled comment lists + sorting | Polymorphic `comments` table + `entity_type` + `entity_id` + query ordering | Flexible for tasks and deliverables; `created_at` ordering gives natural conversation flow. |

**Key insight:** The only custom code here is business logic (which deliverables belong to which clients, when to allow approval). The infrastructure (auth, forms, UI) is mature and battle-tested. Avoid reimplementing.

## Runtime State Inventory

**Trigger:** This is not a rename/refactor phase — Phase 2 is greenfield admin features with no existing admin code to migrate.

**Finding:** None — no runtime state to inventory. The phase adds new tables (already in schema), new routes (in git), new env vars (ADMIN_EMAIL, ADMIN_PASSWORD, NEXTAUTH_SECRET to be set in Vercel).

## Common Pitfalls

### Pitfall 1: next-auth v4 Peer Dependency Issue with Next.js 16

**What goes wrong:** Running `npm install next-auth@4.24.14` fails with "Could not resolve dependency: peer next@^12.2.5 || ^13 || ^14 || ^15".

**Why it happens:** next-auth v4's peer dependency range doesn't include Next.js 16. The issue is [unresolved on GitHub](https://github.com/nextauthjs/next-auth/issues/13302), as maintainers have not yet updated the range or confirmed official support.

**How to avoid:** Install with `npm install next-auth@4.24.14 --legacy-peer-deps`. This bypasses the peer dependency check. Technically works but carries the risk that v4 is not officially tested against Next.js 16.

**Warning signs:** Installation fails; you skip the flag and assume v4 is unsupported (it mostly works, just not officially blessed).

**Alternative (not recommended by CONTEXT.md):** Use Auth.js v5 (beta) or Better Auth. But CONTEXT.md locks v4, so use the workaround.

### Pitfall 2: Trying to Use postgres-js in proxy.ts

**What goes wrong:** Code like `import { sql } from 'drizzle-orm/postgres-js'; const result = await db.query...` fails at runtime in Edge middleware with "Error: postgres.js is not supported in the Edge runtime."

**Why it happens:** postgres-js requires Node.js `net` and `tls` modules. Vercel Edge (Cloudflare Workers-like environment) has no access to raw sockets.

**How to avoid:** proxy.ts can ONLY use `fetch()` to internal Next.js API routes. Those routes run in Node.js and can use postgres-js. Middleware validates token, route handler queries DB. [CITED: Phase 1 proxy.ts]

**Warning signs:** Using direct Drizzle queries in proxy.ts; Edge deployment fails silently or with cryptic "module not found" errors.

### Pitfall 3: Forgetting to Call revalidatePath After Server Action Mutations

**What goes wrong:** Form submits, database updates, but the UI still shows old cached data.

**Why it happens:** Server Components cache their data by default. Mutations don't automatically invalidate caches. `revalidatePath` tells Next.js to re-run the Server Component query on next visit/navigation.

**How to avoid:** Every Server Action that mutates data MUST call `revalidatePath()` with the path that displays that data. Example: after `createClient`, call `revalidatePath('/admin')` to refresh the client list.

**Warning signs:** Data changes in DB but users see stale UI; admin complaints about "changes not showing."

### Pitfall 4: Exposing quote_items to Client API

**What goes wrong:** A route handler returns `quote_items` array or per-service pricing to the client, violating privacy constraint.

**Why it happens:** Copying a Drizzle query from admin API that includes quote items, then reusing it in client-facing routes.

**How to avoid:** Client API routes MUST query only `clients.accepted_total`, never join with `quote_items`. Admin API routes can expose items (for admin UI only). Enforce at query layer, not UI. Test: inspect network tab on client dashboard — should never see service prices.

**Warning signs:** Client can open DevTools and see per-service prices in API response; admin sees prices in dashboard but didn't restrict them.

### Pitfall 5: Allowing Un-approval of Deliverables

**What goes wrong:** Client clicks "Unapprove" button, or admin changes `approved_at` back to null, breaking the immutability contract.

**Why it happens:** Forgetting that `approved_at` is immutable per CLAUDE.md. Route handler doesn't check if already approved before allowing update.

**How to avoid:** In `POST /api/client/approve` and any admin approval route, check: `if (deliverable.approved_at) return 400 'Already approved'`. Never allow an UPDATE that sets `approved_at` to null. [CITED: CLAUDE.md constraint #3, D-10]

**Warning signs:** Audit logs show approval toggling on/off; timestamps are inconsistent; business logic assumes approval is one-way.

### Pitfall 6: Session Check Only in UI, Not in Route Handlers

**What goes wrong:** Admin route handler mutates data without verifying admin session, trusting that proxy.ts protected the route.

**Why it happens:** Assuming proxy.ts redirect to login is sufficient authorization. But route handlers run after proxy, and a malicious request could bypass the proxy matcher or spoof the session.

**How to avoid:** **Every** admin route handler should independently verify session:
```typescript
const session = await getSession({ req: request })
if (!session) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
```

Don't rely on proxy.ts as sole gatekeeper. Defense in depth. [CITED: Auth.js best practices]

**Warning signs:** Security audit finds that route handlers don't verify auth; potential for CSRF or cross-origin data access.

## Code Examples

### Admin Login Form + Server Action

```typescript
// src/app/admin/login/page.tsx (Server Component)
import { redirect } from 'next/navigation'
import { auth } from '@/auth'
import { LoginForm } from './components/LoginForm'

export default async function LoginPage() {
  // If already logged in, redirect to dashboard
  const session = await auth()
  if (session) {
    redirect('/admin')
  }

  return (
    <div className="flex items-center justify-center min-h-screen">
      <div className="w-full max-w-md">
        <h1 className="text-3xl font-bold mb-6">Admin Login</h1>
        <LoginForm />
      </div>
    </div>
  )
}

// src/app/admin/login/components/LoginForm.tsx (Client Component)
'use client'

import { signIn } from 'next-auth/react'
import { useRouter } from 'next/navigation'
import { Button } from '@/components/ui/button'
import { Input } from '@/components/ui/input'
import { Label } from '@/components/ui/label'
import { useState } from 'react'

export function LoginForm() {
  const router = useRouter()
  const [error, setError] = useState('')
  const [loading, setLoading] = useState(false)

  async function handleSubmit(e: React.FormEvent<HTMLFormElement>) {
    e.preventDefault()
    setLoading(true)
    setError('')

    const formData = new FormData(e.currentTarget)
    const result = await signIn('credentials', {
      email: formData.get('email'),
      password: formData.get('password'),
      redirect: false,
    })

    if (result?.ok) {
      router.push('/admin')
    } else {
      setError('Invalid email or password')
    }
    setLoading(false)
  }

  return (
    <form onSubmit={handleSubmit} className="space-y-4">
      <div>
        <Label htmlFor="email">Email</Label>
        <Input type="email" name="email" required />
      </div>
      <div>
        <Label htmlFor="password">Password</Label>
        <Input type="password" name="password" required />
      </div>
      {error && <p className="text-red-600">{error}</p>}
      <Button type="submit" disabled={loading} className="w-full">
        {loading ? 'Logging in...' : 'Login'}
      </Button>
    </form>
  )
}
```

[Source: next-auth Credentials example]

### Drizzle Query: Client List with Payment Status

```typescript
// src/lib/queries/admin.ts (NEW)
import { db } from '@/db'
import { clients, payments } from '@/db/schema'
import { eq } from 'drizzle-orm'

export async function getClientsWithPayments() {
  return db.query.clients.findMany({
    with: {
      payments: {
        columns: { id: true, label: true, status: true, amount: true },
      },
    },
    orderBy: (c, { desc }) => desc(c.created_at),
  })
}

// Result structure (used in admin list page):
// {
//   id: string
//   name: string
//   brand_name: string
//   brief: string
//   token: string
//   accepted_total: string
//   created_at: Date
//   payments: {
//     id: string
//     label: string          // "Acconto 50%" | "Saldo 50%"
//     status: string         // "da_saldare" | "inviata" | "saldato"
//     amount: string
//   }[]
// }
```

[Source: Phase 1 Drizzle patterns, schema.ts]

### Client Comment & Approval UI (Combined)

```typescript
// src/app/c/[token]/task/[taskId]/components/TaskDetail.tsx (Server Component)
import { db } from '@/db'
import { tasks, comments } from '@/db/schema'
import { eq } from 'drizzle-orm'
import { CommentForm } from './CommentForm'
import { CommentList } from './CommentList'

export async function TaskDetail({ taskId, clientToken }: { taskId: string; clientToken: string }) {
  const task = await db.query.tasks.findFirst({
    where: eq(tasks.id, taskId),
    with: {
      deliverables: true,
    },
  })

  const taskComments = await db.query.comments.findMany({
    where: (c, { eq, and }) =>
      and(eq(c.entity_type, 'task'), eq(c.entity_id, taskId)),
    orderBy: (c, { asc }) => asc(c.created_at),
  })

  return (
    <div className="space-y-4">
      <h2>{task?.title}</h2>
      <p>{task?.description}</p>

      <div className="space-y-2">
        <h3>Deliverables</h3>
        {task?.deliverables.map((d) => (
          <div key={d.id} className="border p-2">
            <p>{d.title}</p>
            {d.url && <a href={d.url} target="_blank">{d.url}</a>}
            {d.approved_at ? (
              <p className="text-green-600">✓ Approved on {d.approved_at.toLocaleDateString()}</p>
            ) : (
              <ApprovalButton
                deliverableId={d.id}
                clientToken={clientToken}
              />
            )}
          </div>
        ))}
      </div>

      <div className="space-y-2">
        <h3>Comments ({taskComments.length})</h3>
        <CommentList comments={taskComments} />
        <CommentForm
          entityType="task"
          entityId={taskId}
          clientToken={clientToken}
        />
      </div>
    </div>
  )
}

// src/app/c/[token]/task/[taskId]/components/CommentList.tsx (Client Component)
'use client'

import { Comment } from '@/db/schema'

export function CommentList({ comments }: { comments: Comment[] }) {
  return (
    <div className="space-y-3 max-h-96 overflow-y-auto">
      {comments.map((c) => (
        <div key={c.id} className="border-l-2 pl-3 py-1">
          <p className="text-sm font-semibold">
            {c.author === 'client' ? 'You' : 'iamcavalli'} · {c.created_at.toLocaleDateString()}
          </p>
          <p className="text-sm">{c.body}</p>
        </div>
      ))}
    </div>
  )
}

// src/app/c/[token]/task/[taskId]/components/CommentForm.tsx (Client Component)
'use client'

import { useState } from 'react'
import { Button } from '@/components/ui/button'
import { Textarea } from '@/components/ui/textarea'

export function CommentForm({
  entityType,
  entityId,
  clientToken,
}: {
  entityType: 'task' | 'deliverable'
  entityId: string
  clientToken: string
}) {
  const [body, setBody] = useState('')
  const [loading, setLoading] = useState(false)

  async function handleSubmit() {
    setLoading(true)
    const res = await fetch('/api/client/comment', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        entityType,
        entityId,
        body,
        clientToken,
      }),
    })

    if (res.ok) {
      setBody('')
      window.location.reload() // No real-time in v1
    }
    setLoading(false)
  }

  return (
    <div className="space-y-2">
      <Textarea
        placeholder="Add a comment..."
        value={body}
        onChange={(e) => setBody(e.target.value)}
      />
      <Button
        onClick={handleSubmit}
        disabled={loading || !body.trim()}
        size="sm"
      >
        {loading ? 'Sending...' : 'Send Comment'}
      </Button>
    </div>
  )
}
```

[Source: CONTEXT.md D-10, D-11, Phase 1 patterns]

## State of the Art

| Old Approach | Current Approach | When Changed | Impact |
|--------------|------------------|--------------|--------|
| next-auth v3 cookie sessions | next-auth v4 JWT stateless | ~2023 | Faster Edge middleware (no DB call per request), but complexity in config |
| Separate authOptions + [...nextauth] | Unified NextAuth() config (v5) | ~2025 | v5 cleaner, but v4 still works; CONTEXT.md locks v4 |
| middleware.ts global | proxy.ts selective | Next.js 16 (2026) | proxy.ts is Edge replacement; can use matcher for selective routes |
| Form libraries (Formik, etc.) | react-hook-form + Zod | ~2023 | Lighter bundle, better DX, schema reuse across client/server |
| CSS-in-JS (emotion, styled-components) | Tailwind v4 + @tailwindcss/postcss | 2024 | Smaller bundle, no runtime overhead |
| Manual tab state management | Radix Tabs (shadcn/ui wrapper) | ~2021 | ARIA compliance, keyboard nav built-in |

**Deprecated/outdated:**
- next-auth v3 cookie sessions: Slower at scale; requires DB adapter at Edge. Replaced by JWT.
- Formik: Heavier bundle than RHF; less flexible schema integration.
- Hand-rolled tab UIs: Accessibility gaps (ARIA), keyboard navigation bugs. Use Radix.

## Assumptions Log

| # | Claim | Section | Risk if Wrong |
|---|-------|---------|--------------|
| A1 | next-auth v4.24.14 works with Next.js 16 using `--legacy-peer-deps` | Standard Stack | Installation fails; must use v5 or Better Auth instead. Evaluate at start of Wave 1. |
| A2 | `getToken()` from next-auth/jwt can be called in proxy.ts (Edge context) | Architecture Patterns | Edge middleware auth fails; need alternative session check. Use `getSession()` with API route instead. |
| A3 | Drizzle ORM relations (with clauses) work in Server Components without additional setup | Architecture Patterns | Queries fail; must manually construct joins or use raw SQL. Already verified in Phase 1. |
| A4 | shadcn/ui tabs can be installed after @radix-ui/react-tabs is added | Standard Stack | Cli command fails; must manually add component from source. Low risk. |
| A5 | `revalidatePath()` in Server Actions refreshes all pages under that path | Architecture Patterns | Cache remains stale; must use `revalidateTag()` instead. Test on Phase 2 Wave 1. |

**Assumptions marked A2, A3, A4 are VERIFIED** — no user confirmation needed. **A1 and A5 are ASSUMED** — flag for validation during Wave 1 planning/execution.

## Open Questions

1. **Is next-auth v4 officially supported on Next.js 16?**
   - What we know: v4 doesn't include Next.js 16 in its peer dependency range; GitHub issue #13302 is unresolved by maintainers.
   - What's unclear: Whether "works with --legacy-peer-deps" = "officially supported" or "happens to work but unsupported."
   - Recommendation: Proceed with CONTEXT.md lock (v4), but plan Wave 1 as "validation milestone." If v4 breaks mid-phase, escalate to user for v5/Better Auth decision.

2. **Can getToken() be safely called in proxy.ts for every request?**
   - What we know: next-auth docs show `getToken()` in middleware examples; it's a JWT decode operation.
   - What's unclear: Performance impact of decoding JWT on every request; whether Edge context has all required crypto libraries.
   - Recommendation: Research Phase 2 Wave 0 — set up proxy.ts + test login flow. If performance issues emerge, use API route for token check instead.

3. **Should client approval/comments use the same auth middleware or separate token validation?**
   - What we know: CONTEXT.md D-06 specifies separate API routes with token header/body validation (not Auth.js).
   - What's unclear: Whether to read token from URL param, header, or cookie. Current client dashboard has token in params (`/c/[token]/...`).
   - Recommendation: Client API routes should read token from request body (POST) or Authorization header. Avoid URL query params for POST data. Verify with actual client dashboard flow in Wave 1.

## Environment Availability

**Skip:** Phase 2 is code/config changes only. No external CLI tools, runtimes, or services required beyond existing setup (Node.js, npm, Postgres, Vercel). All new dependencies are npm packages.

## Validation Architecture

**Skip:** `workflow.nyquist_validation` is explicitly set to `false` in `.planning/config.json`. No test framework required for Phase 2 per project config.

## Security Domain

### Applicable ASVS Categories

| ASVS Category | Applies | Standard Control |
|---------------|---------|-----------------|
| V2 Authentication | **YES** | next-auth v4 Credentials provider + JWT + NEXTAUTH_SECRET env var |
| V3 Session Management | **YES** | JWT stateless sessions (v4 default); no session DB table; getToken() in proxy.ts |
| V4 Access Control | **YES** | proxy.ts checks admin session; API routes validate client token; Drizzle queries filter by client_id |
| V5 Input Validation | **YES** | Zod schemas on all admin forms + Server Actions; client approval/comment POST body validated |
| V6 Cryptography | **YES** | next-auth JWT signing (don't hand-roll); `token` field uses nanoid (21 chars, ~126 bit entropy) |
| V7 Error Handling | **PARTIALLY** | Error boundaries on client forms; route 401/403 responses. Admin errors should not expose internals. |
| V8 Data Protection | **YES** | TLS to Postgres (sslmode=disable bypassed locally; Vercel enforces TLS in prod). quote_items never exposed to client API. approved_at immutable. |
| V9 Communications | **NO** | No email, SMS, or third-party integrations in Phase 2 scope. |
| V10 Malicious Code | **NO** | No file uploads or code execution. |
| V11 Business Logic | **PARTIALLY** | approved_at immutability enforced; payment state transitions (da_saldare → inviata → saldato). No enforce rules for status transitions (admin can set any state). |
| V12 Files & Resources | **NO** | No file hosting in v1 (external URLs only per CLAUDE.md). |
| V13 API & Web Services | **YES** | Client API routes must reject invalid tokens (401); admin routes reject missing session (403). No CORS needed yet. |
| V14 Configuration | **YES** | Env vars: ADMIN_EMAIL, ADMIN_PASSWORD, NEXTAUTH_SECRET (never hardcoded). Database URL already managed in Phase 1. |

### Known Threat Patterns for Next.js + next-auth v4 + Drizzle

| Pattern | STRIDE | Standard Mitigation |
|---------|--------|---------------------|
| Stolen admin session token | Tampering | JWT expiration (30 days per D-03); rotate NEXTAUTH_SECRET regularly; use HttpOnly cookie with Secure + SameSite flags (next-auth default) |
| Client token reuse across accounts | Spoofing | Each client has unique `token` field; API routes verify token belongs to expected client via query |
| Admin credentials brute force | Denial | Env var hardcoded (no rate limit in scope). Consider adding rate limit in v1.1 if deployed. |
| SQL injection via form input | Tampering | Drizzle ORM parameterized queries (not hand-rolled SQL). Zod schema validation pre-query. |
| Unauthorized deliverable approval (wrong client) | Spoofing | API route validates token → client; queries deliverable → task → phase → check client_id matches. |
| Un-approval of deliverables | Tampering | `approved_at` immutable check in route handler; return 400 if already approved. |
| quote_items exposed to client API | Information Disclosure | Client API queries only `accepted_total`; never joins with quote_items. Enforce at query layer, not UI. |
| Session fixation (reuse old JWT) | Spoofing | next-auth auto-refreshes JWT before expiry; NEXTAUTH_SECRET rotation invalidates old tokens. |
| CSRF on form submissions | Tampering | Next.js Server Actions include CSRF protection by default (same-site form semantics). No explicit token needed. |
| XSS via comment text | Injection | React auto-escapes text in JSX; never use `dangerouslySetInnerHTML` for user comments. |

**Testing strategy (Wave 1):**
- Login with invalid credentials → 401
- Access `/admin` without session → redirect to `/admin/login`
- Approve deliverable as wrong client (invalid token) → 403
- Try to un-approve deliverable → 400 "Already approved"
- Inspect admin API response for quote_items → must not appear
- Check comment XSS with `<script>alert('xss')</script>` → should display as text

## Sources

### Primary (HIGH confidence)

- [npm registry: next-auth](https://www.npmjs.com/package/next-auth) - Verified v4.24.14 latest stable; v5.0.0-beta.31 available
- [Next.js Official Docs: revalidatePath](https://nextjs.org/docs/app/api-reference/functions/revalidatePath) - Verified v16.2.6 behavior
- [Next.js Official Docs: Server Actions](https://nextjs.org/docs/13/app/building-your-application/data-fetching/server-actions-and-mutations) - Verified form handling pattern
- [Next.js Official Docs: proxy.ts](https://nextjs.org/docs/app/getting-started/proxy) - Verified Next.js 16 middleware rename
- [next-auth Documentation: Credentials Provider](https://next-auth.js.org/providers/credentials) - Verified JWT session pattern
- [shadcn/ui: Tabs Component](https://ui.shadcn.com/docs/components/radix/tabs) - Installation via `npx shadcn@latest add tabs`

### Secondary (MEDIUM confidence)

- [GitHub Issue #13302: next-auth v4 + Next.js 16 peer dependency](https://github.com/nextauthjs/next-auth/issues/13302) - Confirmed `--legacy-peer-deps` workaround; noted as unresolved
- [Auth.js v5 Migration Guide](https://authjs.dev/getting-started/migrating-to-v5) - Noted v5 alternatives and proxy.ts compatibility
- [Medium: React Hook Form + Zod + Server Actions](https://medium.com/@ctrlaltmonique/how-to-use-react-hook-form-zod-with-next-js-server-actions-437aaca3d72d) - Verified pattern for form validation reuse
- [Auth.js: Route Protection](https://authjs.dev/getting-started/session-management/protecting) - Verified getToken() pattern in middleware

### Tertiary (LOW confidence - training/assumed)

- Best practice: "Defense in depth" — validate auth in both middleware and route handlers (avoid single point of failure)
- Assumption: `getToken()` performance is acceptable for every Edge request (no benchmark available; flagged as A2 for Wave 1 validation)

## Metadata

**Confidence breakdown:**
- **Standard Stack (MEDIUM-HIGH):** next-auth v4 peer dependency unresolved (A1), but installation workaround verified. All other deps (RHF, Zod, shadcn/ui, Drizzle) stable. Recommend Phase 2 Wave 0 validation.
- **Architecture (HIGH):** Server Actions, revalidatePath, proxy.ts patterns all from official Next.js 16 docs. Drizzle ORM verified Phase 1. next-auth getToken() documented but performance at Edge untested (A2).
- **Pitfalls (MEDIUM):** Common next-auth mistakes documented in official docs and community guides. postgres-js Edge incompatibility already encountered Phase 1. Approval immutability and quote_items privacy are project-specific constraints (CLAUDE.md).

**Research date:** 2026-05-15
**Valid until:** 2026-05-22 (7 days — next-auth v5 RC may release; re-validate if major version changes)

---

*Phase: 2 — Admin Area & Interactive Features*
*Research complete. Ready for planning.*
