Files
legal-ai/web-ui
Chaim 1f17419ee9
All checks were successful
Build & Deploy / build-and-deploy (push) Successful in 1m44s
ui(precedents): live status pill with shimmer + auto-queue + auto-refresh
The chair pointed out three UX gaps after uploading a new precedent:

1. The status said "מחלץ הלכות" but nothing was actually running — the
   field only meant "halacha_extraction_status != completed", which
   includes the post-upload "pending" state where the local MCP worker
   hasn't been told to drain anything yet. Misleading.

2. The page didn't refresh on its own. The chair had to F5 to see new
   counts after extraction completed.

3. Clicking the trash icon mid-extraction would cascade-delete the row
   while the extractor was still using it (FK errors, partial writes).

Fixes:

- ingest_precedent now auto-queues both metadata and halacha extraction
  on upload by stamping the request timestamps. The chair (or me) drains
  the queue with one `precedent_process_pending` call from chat —
  no need to click any button before that.

- StatusPill is now five-state with proper labels:
    "נכשל" (extraction_status=failed) — red
    "מעבד טקסט" — shimmer (extraction_status=processing)
    "בתור" — neutral (chunks queued, not yet running)
    "מחלץ הלכות" — shimmer (halacha_extraction_status=processing)
    "ממתין לחילוץ" — neutral (queued for local MCP worker)
    "לא חולץ" — neutral (pending without queue stamp — shouldn't happen)
    "X/Y מאושרות" — gold (done, with halachot count)
  The shimmer is a CSS-only sliding-stripe animation defined in globals.

- usePrecedents has a conditional refetchInterval — polls every 5s while
  any row is mid-extraction or queued, then stops once everything settles
  to completed/failed. New helper isPrecedentActive() centralises the
  "is this row mid-something" check so the UI and the destructive-action
  guard agree.

- Trash button is disabled (opacity 30%, tooltip explains) while the row
  is active. Pencil/edit stays enabled — editing metadata fields during
  extraction is safe (last write wins, low-stakes race).

Schema: list_external_case_law now exposes the two *_requested_at
timestamps so the UI can distinguish "queued" from "never asked".

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 12:47:31 +00:00
..

עוזר משפטי — 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:

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

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__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.