Skip to content

Dashboard Development

The dashboard is a Next.js 14 App Router web application that provides a browser-based interface for the same legal analysis skills available in Claude Code. It supports both demo mode (zero API cost, fixture data) and live mode (real Anthropic API calls with SSE streaming).

Tech Stack

TechnologyVersionPurpose
Next.js14App Router framework
React18UI library
TypeScript5.xType safety
Tailwind CSS3.xUtility-first styling
shadcn/uilatestComponent library (Radix UI primitives)
Radix UIlatestAccessible headless components
Provider adapterscurrentAnthropic and OpenAI review analysis
lucide-reactlatestIcons
next-themeslatestDark/light theme support

Setup

bash
cd dashboard
npm install
npm run dev

The development server starts at http://127.0.0.1:3000. Hot module replacement is enabled -- changes to components and pages appear immediately.

TIP

No environment variables are required for demo mode. For live mode, users provide their own provider API key via the browser UI -- it is stored in localStorage and sent with each request. The server never stores API keys.


Available Commands

CommandDescription
npm run devStart Next.js dev server (localhost:3000)
npm run buildProduction build (next build)
npm run lintRun ESLint
npm testCompile TypeScript to .test-dist/ then run node --test
npm run smokeHTTP route smoke tests (scripts/api-smoke.mjs)
npm run ui-smokeHeadless browser UI smoke tests (scripts/ui-smoke.mjs)

WARNING

Always run npm run build before submitting a PR. The CI pipeline runs a production build and will catch type errors that npm run dev might miss.


Test Workflow

Important: Tests Run from Compiled Output

Dashboard tests run against compiled JavaScript, not TypeScript source. The npm test command first compiles .ts files to .test-dist/ using tsc -p tsconfig.test.json, then runs node --test on the emitted .test.js files.

This means: if you edit a .ts test file, you must re-run npm test (which rebuilds) for the change to take effect. Running node --test directly on stale .test-dist/ files will use the old compiled version and will not reflect your edits.

Full Test Run

bash
cd dashboard
npm test

This executes two steps:

  1. tsc -p tsconfig.test.json -- compiles *.test.ts to .test-dist/
  2. node --test .test-dist/**/*.test.js -- runs all compiled tests

Single Test File

After a full npm test has compiled the tests, you can run a single file for faster iteration:

bash
# First compile (only needed once after editing)
npm test

# Then run a single test file repeatedly
node --test .test-dist/lib/review-utils.test.js

Adding a New Test

  1. Create dashboard/lib/your-module.test.ts alongside the module being tested
  2. Use the node:test runner (not Jest or Vitest):
typescript
import { describe, it } from "node:test"
import assert from "node:assert/strict"

describe("yourFunction", () => {
  it("should handle the expected case", () => {
    const result = yourFunction(input)
    assert.strictEqual(result, expected)
  })
})
  1. Run npm test to compile and execute
  2. For faster iteration on a single test, run npm test once then use node --test .test-dist/lib/your-module.test.js

Smoke Tests

bash
npm run smoke       # HTTP route checks (scripts/api-smoke.mjs)
npm run ui-smoke    # Headless browser UI checks (scripts/ui-smoke.mjs)

The smoke tests verify that:

  • All API routes respond with correct status codes
  • The UI renders without JavaScript errors
  • Navigation between pages works
  • Demo mode functions without an API key

Adding a New Page

1. Create the Page Component

Create dashboard/app/your-page/page.tsx:

tsx
"use client"

import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"

export default function YourPage() {
  return (
    <div className="container mx-auto py-8 space-y-6">
      <h1 className="text-3xl font-bold">Your Page Title</h1>
      <Card>
        <CardHeader>
          <CardTitle>Section</CardTitle>
        </CardHeader>
        <CardContent>
          <p>Content goes here.</p>
        </CardContent>
      </Card>
    </div>
  )
}

INFO

All pages that use React hooks or browser APIs must include "use client" at the top. Server Components are the default in Next.js 14 App Router.

2. Add to the Sidebar

Open dashboard/components/sidebar.tsx and add a navigation entry:

tsx
{
  name: "Your Page",
  href: "/your-page",
  icon: YourIcon,
}

Adding an API Route

1. Create the Route Handler

Create dashboard/app/api/your-route/route.ts:

typescript
import { NextRequest, NextResponse } from "next/server"
import { validateReviewRouteBody, mapReviewRouteError } from "@/lib/api-route-utils"

export async function POST(request: NextRequest) {
  try {
    const body = await request.json()
    const validation = validateReviewRouteBody(body)

    if (!validation.ok) {
      return NextResponse.json(
        { error: validation.error },
        { status: validation.status }
      )
    }

    // Your route logic here
    const result = await processRequest(validation)

    return NextResponse.json(result)
  } catch (error) {
    const mapped = mapReviewRouteError(error)
    return NextResponse.json(
      { error: mapped.error },
      { status: mapped.status }
    )
  }
}

2. Follow Existing Patterns

Study the existing route handlers for consistent patterns:

RoutePathPurpose
Reviewapp/api/review/route.tsMain analysis endpoint (supports SSE streaming)
Anthropicapp/api/anthropic/validate/route.tsAPI key validation
Documentapp/api/document/extract/route.tsDocument text extraction
Legislationapp/api/legislation/search/route.tsLegislation search
Compareapp/api/compare/route.tsContract comparison
Generateapp/api/generate/route.tsDocument generation

3. SSE Streaming Pattern

For long-running analysis routes, support SSE when the request Accept header is text/event-stream:

typescript
if (request.headers.get("accept") === "text/event-stream") {
  const stream = streamSkillAnalysis(apiKey, skillId, documentText)

  return new Response(stream, {
    headers: {
      "Content-Type": "text/event-stream",
      "Cache-Control": "no-cache",
      Connection: "keep-alive",
    },
  })
}

// Fallback to buffered JSON for non-streaming clients
const result = await runSkillAnalysis(apiKey, skillId, documentText)
return NextResponse.json(result)

TIP

The SSE protocol uses named events: progress (with stage and percent), result (final review object), and error (error message). Follow this convention for consistency.


Adding a Component

Use Existing UI Primitives

The dashboard uses shadcn/ui components built on Radix UI. Always prefer existing primitives:

ComponentImportCommon Use
Card@/components/ui/cardContent containers
Badge@/components/ui/badgeStatus indicators, tags
Button@/components/ui/buttonActions
Dialog@/components/ui/dialogModal windows
Tabs@/components/ui/tabsTabbed content
Table@/components/ui/tableData tables
Select@/components/ui/selectDropdown selection
Input@/components/ui/inputText inputs

Creating a New Component

tsx
// dashboard/components/my-component.tsx
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
import { Badge } from "@/components/ui/badge"

interface MyComponentProps {
  title: string
  status: "pass" | "fail" | "warning"
  children: React.ReactNode
}

export function MyComponent({ title, status, children }: MyComponentProps) {
  return (
    <Card>
      <CardHeader>
        <div className="flex items-center justify-between">
          <CardTitle>{title}</CardTitle>
          <Badge variant={status === "pass" ? "default" : "destructive"}>
            {status}
          </Badge>
        </div>
      </CardHeader>
      <CardContent>{children}</CardContent>
    </Card>
  )
}

Theming

The dashboard uses CSS variables for theming. All colour values reference hsl(var(--variable)) tokens defined in the global stylesheet. Do not use hard-coded colour values:

tsx
// Correct -- uses theme tokens
className="text-primary bg-muted border-border"

// Incorrect -- hard-coded colours
className="text-blue-600 bg-gray-100 border-gray-300"

Adding a Skill to the Dashboard

To make a new Claude Code skill available in the web UI, update these files:

#FileChange
1lib/skills-data.tsAdd skill definition (id, name, command, category, description)
2lib/api.tsAdd system prompt to SKILL_PROMPTS and type mapping to SKILL_TO_TYPE
3lib/api-route-utils.tsAdd skill ID to VALID_LIVE_REVIEW_SKILLS array
4app/review/page.tsxAdd skill ID to LIVE_SUPPORTED_SKILLS Set

See Adding Skills -- Step 4 for detailed instructions.


Project Structure

dashboard/
  app/
    api/                        # Route handlers
      review/route.ts           # Main analysis (SSE + JSON)
      anthropic/validate/route.ts # API key validation
      document/extract/route.ts # File text extraction
      legislation/              # Legislation lookup routes
      compare/route.ts          # Contract comparison
      generate/route.ts         # Document generation
    review/page.tsx             # Review page
    generate/page.tsx           # Document generator page
    layout.tsx                  # Root layout
    page.tsx                    # Home page
  components/
    ui/                         # shadcn/ui primitives (Card, Badge, Button, etc.)
    sidebar.tsx                 # Navigation sidebar
    review-view.tsx             # Review results display
    demo-*.tsx                  # Demo mode components
  lib/
    api.ts                      # Anthropic SDK: runSkillAnalysis, streamSkillAnalysis
    api-route-utils.ts          # Request validation, error mapping, VALID_LIVE_REVIEW_SKILLS
    skills-data.ts              # Skill catalog (id, name, command, category, description)
    types.ts                    # TypeScript interfaces for all review types
    document-extraction.ts      # TXT / DOCX / PDF text extraction
    document-ocr.ts             # OCR fallback (OpenAI vision)
    review-utils.ts             # Review lifecycle helpers
    storage-utils.ts            # Client-side persistence helpers
    storage.ts                  # localStorage wrapper
    legislation.ts              # Statute data
    linkify-statutes.tsx        # Statute link rendering in JSX
    demo-data/                  # Fixtures for demo mode (zero API cost)
  public/                       # Static assets
  .test-dist/                   # Compiled test output (gitignored)
  tsconfig.json                 # Main TypeScript config
  tsconfig.test.json            # Test TypeScript config

AI Legal UK · The Counsel — Established MMXXVI · Built for England & Wales · Not legal advice.