Role labels: ceo→מנהל, researcher→חוקר, engineer→מהנדס, qa→בודק איכות Issue status: in_progress→בביצוע, done→הושלם, todo→לביצוע, etc. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
עוזר משפטי — 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-rewritebranch 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 insrc/app/globals.css
Local development
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:
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__deploywith app UUIDl146g36mtlp0k03vrwkyrgkk) 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.