diff --git a/.taskmaster/tasks/tasks.json b/.taskmaster/tasks/tasks.json index 4ad3909..a988724 100644 --- a/.taskmaster/tasks/tasks.json +++ b/.taskmaster/tasks/tasks.json @@ -848,13 +848,13 @@ "description": "Port the remaining 5 views. Use TanStack Table for training corpus and diagnostics lists. Port any charts/visualizations from current index.html. Plan: ~/.claude/plans/joyful-marinating-sutton.md.", "details": "See full plan at ~/.claude/plans/joyful-marinating-sutton.md for architecture, critical files, risks, and open questions. This task is phase 5 of 7 in the legal-ai UI rewrite from vanilla HTML to Next.js 15 + shadcn/ui.", "testStrategy": "Feature parity with old legal-ai/web/static/index.html across all 10 views.", - "status": "in-progress", + "status": "done", "dependencies": [ "86" ], "priority": "medium", "subtasks": [], - "updatedAt": "2026-04-11T17:28:06.562Z" + "updatedAt": "2026-04-11T17:33:42.976Z" }, { "id": "88", @@ -862,12 +862,13 @@ "description": "Accessibility pass (keyboard nav, aria-label on RTL icons, focus trap in modals). Error boundaries + toast notifications for failed mutations. Loading states for every query. Cross-browser smoke test (Chrome, Firefox, Safari) + mobile device test. Document E2E smoke test script in web-ui/README.md. Plan: ~/.claude/plans/joyful-marinating-sutton.md.", "details": "See full plan at ~/.claude/plans/joyful-marinating-sutton.md for architecture, critical files, risks, and open questions. This task is phase 6 of 7 in the legal-ai UI rewrite from vanilla HTML to Next.js 15 + shadcn/ui.", "testStrategy": "Lighthouse a11y score > 90, all loading states visible, errors show toasts, README has documented smoke test steps.", - "status": "pending", + "status": "in-progress", "dependencies": [ "87" ], "priority": "medium", - "subtasks": [] + "subtasks": [], + "updatedAt": "2026-04-11T17:40:09.247Z" }, { "id": "89", @@ -899,9 +900,9 @@ ], "metadata": { "version": "1.0.0", - "lastModified": "2026-04-11T17:28:06.563Z", + "lastModified": "2026-04-11T17:40:09.248Z", "taskCount": 59, - "completedCount": 54, + "completedCount": 55, "tags": [ "master" ] diff --git a/web-ui/README.md b/web-ui/README.md index e215bc4..c7505d9 100644 --- a/web-ui/README.md +++ b/web-ui/README.md @@ -1,36 +1,175 @@ -This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-reference/cli/create-next-app). +# עוזר משפטי — Web UI (Next.js rewrite) -## Getting Started +The Next.js 16 rewrite of `legal-ai.nautilus.marcusgroup.org`, currently hosted side-by-side with the legacy vanilla `index.html` at: -First, run the development server: +- **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 run dev -# or -yarn dev -# or -pnpm dev -# or -bun dev +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 ``` -Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. +### API connection -You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file. +By default the dev server proxies to production FastAPI (`https://legal-ai.nautilus.marcusgroup.org`). To point at a different backend, set: -This project uses [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load [Geist](https://vercel.com/font), a new font family for Vercel. +```bash +export NEXT_PUBLIC_API_ORIGIN=http://localhost:8000 +npm run dev +``` -## Learn More +## Project layout -To learn more about Next.js, take a look at the following resources: +``` +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() +``` -- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API. -- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial. +## Smoke test (run after every deploy) -You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome! +Use any browser at the staging URL. Every step should be doable **without console errors** and each mutation should produce a visible toast. -## Deploy on Vercel +### 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` -The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js. +### 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}` -Check out our [Next.js deployment documentation](https://nextjs.org/docs/app/building-your-application/deploying) for more details. +### 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. diff --git a/web-ui/src/app/error.tsx b/web-ui/src/app/error.tsx new file mode 100644 index 0000000..4075f0f --- /dev/null +++ b/web-ui/src/app/error.tsx @@ -0,0 +1,57 @@ +"use client"; + +import { useEffect } from "react"; +import Link from "next/link"; +import { AlertTriangle } from "lucide-react"; +import { Button } from "@/components/ui/button"; + +/* + * Route-segment error boundary. Next 16 App Router convention: this file + * catches render-time errors thrown below the root layout. `reset` clears + * the error and re-renders the segment; we keep the user's nav in place + * (no AppShell here — the shell re-renders from the layout above us). + */ +export default function ErrorPage({ + error, + reset, +}: { + error: Error & { digest?: string }; + reset: () => void; +}) { + useEffect(() => { + console.error("Route error:", error); + }, [error]); + + return ( +
+
+
+
+ ); +} diff --git a/web-ui/src/app/global-error.tsx b/web-ui/src/app/global-error.tsx new file mode 100644 index 0000000..60157f6 --- /dev/null +++ b/web-ui/src/app/global-error.tsx @@ -0,0 +1,60 @@ +"use client"; + +/* + * Root-level error boundary. Renders only when the root layout itself + * crashes, so it must include its own / . No AppShell here — + * the Providers that wrap AppShell are also above the crash boundary and + * may themselves be the thing that failed. + */ +export default function GlobalError({ + error, + reset, +}: { + error: Error & { digest?: string }; + reset: () => void; +}) { + return ( + + +
+

+ שגיאה חמורה +

+

+ האפליקציה נכשלה לטעון. נסה לרענן את הדף. +

+ {error.digest && ( +

+ error id: {error.digest} +

+ )} + +
+ + + ); +} diff --git a/web-ui/src/app/layout.tsx b/web-ui/src/app/layout.tsx index 3414145..e32db5f 100644 --- a/web-ui/src/app/layout.tsx +++ b/web-ui/src/app/layout.tsx @@ -14,8 +14,11 @@ const heebo = Heebo({ }); export const metadata: Metadata = { - title: "עוזר משפטי — ניהול תיקים", - description: "מערכת סיוע בניסוח החלטות לוועדת ערר לתכנון ובנייה", + title: { + default: "עוזר משפטי — ניהול תיקים", + template: "%s · עוזר משפטי", + }, + description: "מערכת סיוע בניסוח החלטות לוועדת ערר לתכנון ובנייה, ירושלים", }; export default function RootLayout({ diff --git a/web-ui/src/app/not-found.tsx b/web-ui/src/app/not-found.tsx new file mode 100644 index 0000000..17428a8 --- /dev/null +++ b/web-ui/src/app/not-found.tsx @@ -0,0 +1,24 @@ +import Link from "next/link"; +import { AppShell } from "@/components/app-shell"; +import { Button } from "@/components/ui/button"; + +export default function NotFound() { + return ( + +
+ +
+

הדף לא נמצא

+

+ הכתובת שביקשת אינה קיימת או שהוזזה לדף אחר. +

+
+ +
+
+ ); +} diff --git a/web-ui/src/components/app-shell.tsx b/web-ui/src/components/app-shell.tsx index 6bfb8a6..bfd5187 100644 --- a/web-ui/src/components/app-shell.tsx +++ b/web-ui/src/components/app-shell.tsx @@ -1,5 +1,8 @@ +"use client"; + import type { ReactNode } from "react"; import Link from "next/link"; +import { usePathname } from "next/navigation"; /** * Ezer Mishpati navigation shell. @@ -9,8 +12,9 @@ import Link from "next/link"; * - Parchment/cream body background (set on via globals.css) * - Hebrew RTL throughout (set on in layout.tsx) * - * Structure mirrors the current vanilla index.html header so that visual - * continuity is preserved while we migrate screen-by-screen. + * Nav items pick up an `aria-current="page"` and a gold underline when + * the current route matches, so screen readers announce the active + * section and sighted users can see where they are. */ type NavItem = { @@ -19,14 +23,21 @@ type NavItem = { }; const NAV_ITEMS: NavItem[] = [ - { href: "/", label: "בית" }, - { href: "/upload", label: "העלאת מסמכים" }, - { href: "/training", label: "אימון סגנון" }, - { href: "/skills", label: "מיומנויות" }, - { href: "/diagnostics", label: "אבחון" }, + { href: "/", label: "בית" }, + { href: "/cases/new", label: "תיק חדש" }, + { href: "/training", label: "אימון סגנון" }, + { href: "/skills", label: "מיומנויות" }, + { href: "/diagnostics", label: "אבחון" }, ]; +function isActive(pathname: string, href: string): boolean { + if (href === "/") return pathname === "/"; + return pathname === href || pathname.startsWith(`${href}/`); +} + export function AppShell({ children }: { children: ReactNode }) { + const pathname = usePathname(); + return ( <>
ניהול תיקים -
-
+
{children}
diff --git a/web-ui/src/components/training/corpus-panel.tsx b/web-ui/src/components/training/corpus-panel.tsx index 3dbb3ec..12956f3 100644 --- a/web-ui/src/components/training/corpus-panel.tsx +++ b/web-ui/src/components/training/corpus-panel.tsx @@ -79,9 +79,10 @@ function Row({ item }: { item: CorpusDecision }) { size="sm" onClick={onDelete} disabled={del.isPending} + aria-label={`הסר את ${item.decision_number || "החלטה זו"} מהקורפוס`} className="text-danger hover:text-danger hover:bg-danger-bg" > - +