ui(precedents): collapsible groups by precedent + Hebrew labels + RTL fixes
All checks were successful
Build & Deploy / build-and-deploy (push) Successful in 33s

After running the dual-mode halacha extractor on a real appeals committee
decision (403-17), the pending-review tab surfaced 351 halachot in a
single flat list — the chair correctly pointed out that this is unusable
without grouping. Three fixes:

1. Group pending halachot by precedent (case_law_id). Each group shows
   the citation, court, date, level and item count; default state is
   collapsed so the chair picks one ruling at a time. Within a group,
   items still sort by confidence ascending so the doubtful ones surface
   first. J/K/A/R/E now scope to currently-expanded groups; toggling
   open auto-focuses the first item.

2. Translate the badges that were leaking English: rule_type values
   (`persuasive`, `interpretive`, `binding`, `application`, `procedural`,
   `obiter`) now render as Hebrew labels, and `confidence X.XX` becomes
   `ביטחון X.XX`. The card header no longer repeats the citation since
   it's already in the group header.

3. Strip Unicode bidi marks (U+200E/F/202A-E/2066-9) from displayed
   citations. Nevo PDFs and the upload form embed these in the
   case_number; they render as zero-width but visually push the text
   away from the right edge of the table cell. Also: hide the empty
   court line under the case name in the list (was rendering as a
   stray em-dash), and use a muted em-dash for empty date/level rather
   than blank/dash inconsistency across columns.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-03 12:05:40 +00:00
parent 2cfdf35191
commit fc3b6b6cae
2 changed files with 240 additions and 98 deletions

View File

@@ -32,6 +32,15 @@ function formatDate(iso: string | null) {
}
}
/* The upload form (and Nevo PDFs) embed Unicode bidi marks (RTL/LTR/embedding/
* isolate) inside the citation. They render as zero-width but visually push
* the text away from the cell edge. Strip them for display only — DB still
* has the original. */
function cleanCitation(s: string | null | undefined): string {
if (!s) return "—";
return s.replace(/[--]/g, "").trim();
}
function StatusPill({ p }: { p: Precedent }) {
if (p.extraction_status === "failed") {
return <Badge variant="outline" className="bg-danger-bg text-danger border-danger/40">נכשל</Badge>;
@@ -75,14 +84,18 @@ function PrecedentRow({
return (
<TableRow className="border-rule hover:bg-gold-wash/30">
<TableCell className="font-semibold text-navy" dir="ltr">
{p.case_number}
<TableCell className="font-semibold text-navy text-right" dir="rtl">
<span dir="auto">{cleanCitation(p.case_number)}</span>
</TableCell>
<TableCell className="text-ink">
<div className="font-medium">{p.case_name || "—"}</div>
<div className="text-[0.72rem] text-ink-muted">{p.court || "—"}</div>
<div className="font-medium">{cleanCitation(p.case_name)}</div>
{p.court ? (
<div className="text-[0.72rem] text-ink-muted">{p.court}</div>
) : null}
</TableCell>
<TableCell className="text-ink-muted">
{p.date ? formatDate(p.date) : <span className="text-ink-light"></span>}
</TableCell>
<TableCell className="text-ink-muted">{formatDate(p.date)}</TableCell>
<TableCell>
{p.practice_area ? (
<Badge variant="outline" className="bg-navy-soft/40 text-navy border-navy/30">
@@ -93,7 +106,11 @@ function PrecedentRow({
)}
</TableCell>
<TableCell className="text-ink-muted text-[0.78rem]">
{p.precedent_level || "—"}
{p.precedent_level ? (
p.precedent_level
) : (
<span className="text-ink-light"></span>
)}
</TableCell>
<TableCell>
<StatusPill p={p} />