# עוזר משפטי — Web UI (Next.js rewrite)

The Next.js 16 rewrite of `legal-ai.nautilus.marcusgroup.org`, currently hosted side-by-side with the legacy vanilla `index.html` at:

- **Staging:** https://legal-ai-next.nautilus.marcusgroup.org (auto-deployed from `ui-rewrite` branch via Coolify)
- **Production FastAPI:** https://legal-ai.nautilus.marcusgroup.org (same backend, old UI still default)

The rewrite talks to the existing FastAPI via proxy rewrites in `next.config.ts` — no CORS setup, no duplicated backend.

## Stack

- Next.js 16.2.3 (App Router, Turbopack, `output: "standalone"`)
- React 19.2 · TypeScript · Tailwind v4 · shadcn/ui (radix-nova preset)
- TanStack Query v5 + TanStack Table v8
- react-hook-form + zod for mutations
- react-dropzone for uploads; EventSource for SSE progress
- Heebo via `next/font/google`; design tokens in `src/app/globals.css`

## Local development

```bash
npm install
npm run dev        # http://localhost:3000
npm run build      # full type check + production build
npm run lint
npm run api:types  # regenerate src/lib/api/types.ts from FastAPI's OpenAPI
```

### API connection

By default the dev server proxies to production FastAPI (`https://legal-ai.nautilus.marcusgroup.org`). To point at a different backend, set:

```bash
export NEXT_PUBLIC_API_ORIGIN=http://localhost:8000
npm run dev
```

## Project layout

```
src/
├── app/                    # Route segments (App Router)
│   ├── layout.tsx          # Root: Providers + RTL html + fonts
│   ├── page.tsx            # Home: KPIs + status donut + cases table
│   ├── error.tsx           # Route-segment error boundary
│   ├── global-error.tsx    # Root crash fallback
│   ├── not-found.tsx       # 404
│   ├── cases/
│   │   ├── new/            # 3-step create wizard
│   │   └── [caseNumber]/
│   │       ├── page.tsx    # Case detail (tabs + workflow timeline)
│   │       └── compose/    # Research analysis + chair-position editor
│   ├── training/           # Style portrait + corpus + compare (3 tabs)
│   ├── skills/             # Paperclip skills inventory
│   └── diagnostics/        # DB health + failed/stuck docs
├── components/
│   ├── app-shell.tsx       # Header + nav with aria-current
│   ├── cases/              # Home + detail screens
│   ├── compose/            # Research analysis editor
│   ├── documents/          # UploadSheet
│   ├── training/           # Style report / corpus / compare panels
│   ├── wizard/             # Case create wizard + parties-field
│   └── ui/                 # shadcn primitives
├── lib/
│   ├── api/                # Typed hooks per domain (cases, documents, research,
│   │                       #   system, skills, training)
│   ├── schemas/            # zod schemas (case create / update)
│   ├── practice-area.ts    # Multi-tenant axis enum + deriveSubtype()
│   ├── sse.ts              # EventSource wrapper
│   ├── providers.tsx       # QueryClient + Toaster
│   └── utils.ts            # cn()
```

## Smoke test (run after every deploy)

Use any browser at the staging URL. Every step should be doable **without console errors** and each mutation should produce a visible toast.

### 1. Home · `/`
- [ ] Header nav shows 5 items; the current page is underlined in gold
- [ ] 4 KPI cards render real numbers (סה״כ · בהכנה · בכתיבה · מוכנים)
- [ ] Cases table lists existing cases; search filters by case number or title
- [ ] "פיזור סטטוסים" donut renders with a legend
- [ ] "+ תיק חדש" button in the top-left navigates to `/cases/new`

### 2. Create case · `/cases/new`
- [ ] 3-step wizard: פרטי יסוד → צדדים → השלמות
- [ ] Type `1500-25` → appeal_subtype auto-fills to "רישוי ובנייה"
- [ ] Type `8500-25` → subtype auto-fills to "היטל השבחה"
- [ ] Manually pick a different subtype → auto-fill stops
- [ ] Submitting with invalid case number shows a zod field error (no crash)
- [ ] Successful create → toast "תיק חדש נוצר" → router pushes to `/cases/{number}`

### 3. Case detail · `/cases/[caseNumber]`
- [ ] Header shows status badge + gold "ועדת ערר · X" practice-area badge
- [ ] Tabs switch cleanly: סקירה / מסמכים / פעולות
- [ ] Workflow timeline on the right shows the current phase highlighted in gold
- [ ] פעולות tab → "עריכת פרטי תיק" dialog opens; submitting updates the header without full reload (optimistic cache patch)
- [ ] "העלאת מסמכים" sheet opens from the tab row; drag-drop fires a POST and a live progress bar appears via SSE

### 4. Compose · `/cases/[caseNumber]/compose`
- [ ] If analysis-and-research.md exists: threshold claims + issues render as collapsible cards
- [ ] Chair-position textarea auto-saves on blur with "✓ נשמר {time}" indicator
- [ ] If 404 (no analysis yet): empty state card renders, no error toast

### 5. Training · `/training`
- [ ] **Report tab:** headline card, 4 KPIs, subject donut, anatomy bars, top-12 signature phrases
- [ ] **Corpus tab:** table of corpus decisions with a trash icon per row (aria-label present)
- [ ] Deleting a decision refreshes both the corpus table and the report KPIs
- [ ] **Compare tab:** two Selects, pick 2 different decisions, side-by-side panels + shared/only-A/only-B pattern lists

### 6. Skills · `/skills`
- [ ] Card grid of Paperclip skills with sync-status badges (מסונכרן / DB בלבד / לא סונכרן)
- [ ] Chars + file counts render; "לא ידוע" doesn't appear for installed skills

### 7. Diagnostics · `/diagnostics`
- [ ] DB status card shows "מחובר" in green
- [ ] Table counts populate for cases / documents / chunks / corpus / patterns
- [ ] Failed + stuck document lists render (empty states OK)
- [ ] Page self-refreshes every 10s — check the network tab for recurring calls

### 8. Error boundary
- [ ] Visit `/cases/NOT-REAL-999-99` → case detail shows an error card with the FastAPI message and "חזרה לרשימת התיקים" button (no white screen)
- [ ] Visit `/anything-broken-xyz` → custom 404 page with "חזרה לבית" button

### 9. Keyboard + RTL
- [ ] Tab through the home page — focus rings are gold, visible
- [ ] Wizard progresses via Enter on the "הבא" button
- [ ] Screen reader announces nav items with "עמוד נוכחי" on the active one

## Deploy

```
git push  # → Coolify auto-build on branch ui-rewrite (~90 s)
```

> **Known issue:** the Gitea → Coolify webhook is not firing at the time of writing. Trigger a manual deploy via the Coolify MCP (`mcp__coolify__deploy` with app UUID `l146g36mtlp0k03vrwkyrgkk`) or the Coolify UI until the webhook is fixed.

## Phase tracking

See `~/.claude/plans/joyful-marinating-sutton.md` for the 7-phase rewrite plan and `~/legal-ai-ui-rewrite/.taskmaster/tasks/tasks.json` for the task board.

| Phase | Scope | Status |
|---|---|---|
| 1 | Scaffold + Coolify staging | ✅ |
| 2 | API client + typed hooks + probe | ✅ |
| 3 | Read views (home, case detail, compose) | ✅ |
| 4 | Mutations (wizard, edit, upload+SSE) | ✅ |
| 4.5 | Practice-area integration | ✅ |
| 5 | Secondary screens (training, skills, diagnostics) | ✅ |
| 6 | Polish, a11y, error boundaries, smoke test | ✅ |
| 7 | DNS cutover to production | pending |

## Backend contract

The new UI consumes the existing FastAPI at `legal-ai/web/app.py`. Key endpoints currently relied on:

| Endpoint | Hook | Used by |
|---|---|---|
| `GET /api/cases?detail=true` | `useCases` | home table, KPIs |
| `GET /api/cases/{n}/details` | `useCase` | case detail |
| `POST /api/cases/create` | `useCreateCase` | wizard |
| `PUT /api/cases/{n}` | `useUpdateCase` | inline edit |
| `DELETE /api/cases?case_number=...` | (MCP only so far) | admin cleanup |
| `POST /api/cases/{n}/documents/upload-tagged` | `useUploadDocument` | upload sheet |
| `GET /api/progress/{task_id}` (SSE) | `useProgress` | upload progress |
| `GET /api/cases/{n}/research/analysis` | `useResearchAnalysis` | compose |
| `PATCH .../chair-position` | `useSaveChairPosition` | chair editor |
| `GET /api/training/style-report` | `useStyleReport` | training/report tab |
| `GET /api/training/corpus` | `useCorpus` | training/corpus tab |
| `GET /api/training/compare` | `useCompare` | training/compare tab |
| `DELETE /api/training/corpus/{id}` | `useDeleteCorpusEntry` | corpus tab |
| `GET /api/system/diagnostics` | `useDiagnostics` | diagnostics page |
| `GET /api/admin/skills` | `useSkills` | skills page |

Any new endpoint should get a typed hook in `src/lib/api/` — do not reach into `fetch` from component code.
