ui(precedents): live status pill with shimmer + auto-queue + auto-refresh
All checks were successful
Build & Deploy / build-and-deploy (push) Successful in 1m44s

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>
This commit is contained in:
2026-05-03 12:47:31 +00:00
parent 4a9a6b7970
commit 1f17419ee9
5 changed files with 131 additions and 13 deletions

View File

@@ -1833,6 +1833,8 @@ async def list_external_case_law(
appeal_subtype, source_type, precedent_level, is_binding,
summary, headnote, subject_tags, source_kind,
extraction_status, halacha_extraction_status,
metadata_extraction_requested_at,
halacha_extraction_requested_at,
created_at,
(SELECT COUNT(*) FROM halachot h WHERE h.case_law_id = case_law.id) AS halachot_count,
(SELECT COUNT(*) FROM halachot h WHERE h.case_law_id = case_law.id
@@ -1843,7 +1845,15 @@ async def list_external_case_law(
LIMIT ${idx} OFFSET ${idx + 1}
"""
rows = await pool.fetch(sql, *params)
return [_row_to_case_law(r) for r in rows]
out = []
for r in rows:
d = _row_to_case_law(r)
# Render timestamps as ISO strings so the JSON layer stays simple
for k in ("metadata_extraction_requested_at", "halacha_extraction_requested_at"):
if d.get(k) is not None:
d[k] = d[k].isoformat()
out.append(d)
return out
async def delete_case_law(case_law_id: UUID) -> bool:

View File

@@ -190,19 +190,23 @@ async def ingest_precedent(
# Pipeline split: the container does the non-LLM half (extract +
# chunk + embed + store). LLM-driven extraction (metadata, halachot)
# runs separately via the MCP tools `precedent_extract_metadata` /
# `precedent_extract_halachot` from local Claude Code, where
# `claude` CLI is available. Mark statuses so the chair can see
# what's pending in the UI.
# runs separately via the MCP tool `precedent_process_pending` from
# local Claude Code, where `claude` CLI is available.
#
# We auto-queue both extractions so the chair doesn't need to click
# any button — the moment they (or me) run `precedent_process_pending`
# in chat, both kinds get processed.
await db.set_case_law_extraction_status(case_law_id, "completed")
await db.set_case_law_halacha_status(case_law_id, "pending")
await db.request_metadata_extraction(case_law_id)
await db.request_halacha_extraction(case_law_id)
await progress(
"completed",
100,
f"הוכנס לספרייה: {stored_chunks} chunks. "
f"חילוץ הלכות ומטא-דאטה — להפעיל מ-Claude Code "
f"(precedent_extract_halachot / precedent_extract_metadata).",
f"חילוץ הלכות ומטא-דאטה ממתינים בתור — "
f"להפעיל מ-Claude Code: precedent_process_pending.",
)
return {