The 3 drafts cards rendered final-decision before draft-files; mockup 18h has
the draft-files card first, then final+learning, then feedback. Switched the
container to flex-col and gave each card an explicit `order` so the visual order
matches the mockup without reordering any JSX (zero-risk).
tsc --noEmit + eslint clean.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Completes mockup 18i beyond the earlier minimal version:
- AgentRoster — a 5-up grid of agents, each with a status dot, name, role label
and its current activity (derived from the agent's most recent comment).
Replaces the simple AgentStatusWidget list at the top of the feed (reset still
lives in the banner status-rail popover).
- "ממתין לתשובתך" now renders the actual pending InteractionCards (the
approve/reject/answer forms) at the top, not just a count pointer — and those
pending interactions are excluded from the timeline below so they aren't shown
twice.
All interaction logic / mutations preserved. tsc --noEmit + eslint clean.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Fixes the case where the H1 chip, the pipeline stepper and the manual-changer
dropdown disagreed (e.g. 1043-02-26 on analyst_verified): the status sat between
the canonical 10 and a legacy bucket, so each surface read a different list.
Root fix — ONE authoritative status model + canonicalize the real intermediate
states (chair-approved):
- New mcp-server/.../case_status_model.py — the single registry: ordered
StatusDef list (key/label/description/phase/selectable/terminal/on_enter),
the 5 phases, STATUS_ORDER, phase_of/label_of, and a drift assertion that the
CaseStatus enum matches it. `on_enter` is the seam for a status to *do*
something on entry, declared next to its definition (no dispatcher wired yet).
- models.CaseStatus enum: analyst_verified + research_complete promoted to
first-class canonical statuses (the agents set them — they belong in the
model, not a legacy fallback).
- tools/cases.py: the forward-only STATUS_ORDER guard now derives from the
registry instead of an inline list.
- GET /api/status-model exposes the model so the frontend mirror can be
generated against it.
- web-ui case-status.ts mirrors it: the two intermediates moved from the
legacy map into CASE_STATUSES / PHASES(thinking) / STATUS_LABELS /
STATUS_DESCRIPTIONS; status-badge icons+tones extended. So the chip (status),
stepper (its phase) and dropdown (now includes it) all agree.
Invariants: G2 — collapses the scattered status definitions (enum, inline
STATUS_ORDER, two frontend label maps) onto one backend authority + a documented
frontend mirror; no parallel status list remains. Agent-prompt alignment +
backfill follow in a separate change. py_compile + tsc + eslint clean.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Groups the drafts/comments tab into distinct cards instead of one long scroll:
- "פסיקה שצוטטה" (CitationsSection) moved inside the chair's signed-final card
as its sub-section (was floating between final and the files table).
- "קבצי טיוטה" and "הערות יו״ר" sections each wrapped in a bordered surface card.
Result: draft-ready banner + active-draft badge at top, then three cards —
final-decision+learning (gold), draft files, chair feedback. All logic,
mutations, dialogs and the feedback list preserved verbatim.
tsc --noEmit + eslint clean. Design-gate: mockup 18h approved in Claude Design X17.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Agents tab: render the AgentStatusWidget roster at the top of the activity
feed ("who's doing what right now") and surface pending agent interactions in
an amber "ממתין לתשובתך" banner above the timeline (mockup 18i key wins).
- Drafts tab: Hebraize the drafts-table headers File/Size/Date → קובץ/גודל/תאריך
(mockup 18h).
All existing feed logic, mutations and interaction forms preserved.
tsc --noEmit + eslint clean.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Chair-requested refinements to the case banner:
- Tabs now span the full band width via justify-between — wide gaps with the
current 6 tabs, tightening as more are added (gap-2 no-overlap floor).
- Parties moved out of the always-on header line into a "צדדים" Popover, so the
band is shorter and the tab content sits higher on the page.
- Case subject (title) moved up onto the case-number line in a smaller font,
instead of its own line below.
tsc --noEmit + eslint clean.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Folds the standalone /compose decision-editor route into the main case page so
there is no more "sub-page" (mockup 18f), with the final 6-tab workflow order:
סקירה · טיעונים ועמדות · אימות פסיקה · ההחלטה · טיוטות והערות · סוכנים.
- New PositionsPanel — the chair's editing surface (threshold claims + issues
SubsectionCards + ChairEditor + background prose + case precedents + finish
rail), extracted verbatim from the former /compose "עמדות וטענות" tab. Drops
only the doc-strip (the overview DocumentsPanel is the sole owner — no dup).
- "טיעונים ועמדות" tab = PositionsPanel above + the aggregated by-party
LegalArgumentsPanel in a collapsible below.
- "אימות פסיקה" promoted to its own top-level tab (CitationVerificationPanel,
was /compose tab 3); placed BEFORE "ההחלטה" — verify citations, then write
(INV-AH: the writer cites only verified precedents).
- "ההחלטה" stays the single 12-block editor (DecisionBlocksPanel) — no longer
duplicated between /compose and the case page.
- Tabs are now controlled; the "פתח עורך החלטה" CTA switches to the ההחלטה tab
instead of navigating to the deleted route.
- /compose route deleted; header-context /compose special-case removed.
No duplication remains (G2): blocks editor single, docs single (overview),
citation-verify single. Design-gate: mockup 18f approved in Claude Design X17.
tsc --noEmit + eslint clean.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Implements the approved X17 case-detail redesign foundation (mockups 18c "A" +
18e "V2"):
- New StatusRail component — the integrated status strip (current-phase chip,
expected-outcome semantic tag, hearing/updated/sync meta inline, agents
Popover, manual-changer Popover) over the 5-phase pipeline stepper. Lives
INSIDE the parchment band (CaseHeader), so status is now visible on EVERY tab
(supersedes the overview-only StatusHero / reverses the prior INV-IA1
overview-only decision, deliberately).
- CaseHeader absorbs the rail and drops its separate hearing/updated/sync dl
(those moved into the rail — no duplication).
- Tabs restyled to the V2 "segmented" look (each tab a pill, active = raised
white card) with per-tab icons.
- Overview tab body is now just documents + agent-activity preview (status left
the body).
- StatusHero deleted — its content moved wholesale into StatusRail, so no
parallel status surface remains (G2).
Reuses the shared StatusChanger / StatusGuide / AgentStatusWidget verbatim
(G2). Design-gate: mockups 18c + 18e approved in Claude Design X17.
tsc --noEmit + eslint clean.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
A case parked on a status the trimmed PHASES list dropped — but the agents
still set, e.g. `analyst_verified` (HEARTBEAT.md), `research_complete`
(legal-researcher) — rendered with NO active phase: phaseIndexOf returned -1,
so the pipeline stepper went fully blank and the chair couldn't see where the
case stood (reported on 1043-02-26, currently at analyst_verified).
Add LEGACY_STATUS_PHASE in case-status.ts mapping each legacy/intermediate
status to its display phase (analyst_verified/research_complete → "ניתוח וכיוון"),
and a single phaseIndexOf() exported from the SSoT. status-hero.tsx and
workflow-timeline.tsx both dropped their identical local phaseIndexOf (which had
the bug) and import the shared one — no parallel path (G2).
Display-only data-mapping fix; the stepper design is unchanged. Does NOT address
the root drift (agents still set analyst_verified) — that prompt-alignment is a
separate, carefully-sequenced follow-up needing cross-company agent sync.
Invariants: G2 (single phase-resolution path, legacy handled in the SSoT, no
new parallel logic). No backend / status-model change.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Storage stays UTC (DB TIMESTAMPTZ, API ISO-UTC) — only the display layer is
localized, and now deterministically: every timestamp renders pinned to
Asia/Jerusalem via a single Intl-based formatter, so SSR (UTC container) and
the browser agree on any runtime. No layout/visible-format change — only the tz.
- New single date formatter web-ui/src/lib/format-date.ts (G2): formatDate /
formatDateShort / formatDateLong / formatDateTime / formatDateTimeFull /
formatTime / formatIsoDate + Israel helpers getIsraelYear / israelDayKey /
israelMidnightMs / israelParts + formatRelative (long/short/tight wording).
- Routed all ad-hoc toLocaleDateString/toLocaleTimeString/toLocaleString +
hand-rolled new Date(...).get*() / toISOString().slice(0,10) timestamp call
sites (30 files) through it. Number toLocaleString left untouched.
- Spec: added INV-UI9 to docs/spec/X6 (UTC storage, Asia/Jerusalem display).
Display-only; no layout/design/IA change → Claude Design gate N/A.
Invariants: G2 (single date formatter, no parallel ad-hoc formatting), INV-UI9.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Adds a one-click 'reset agents' action for cases where writer/QA agents
are stuck in Paperclip's error state (triggered by recovery loop or
failed run). Addresses the root cause documented in
reference_paperclip_recovery_loops: reassigns open issues from agents
back to the chair user, and calls reset_agent_session for each error
agent to clear wedged runtime sessions.
Changes:
- paperclip_client.py: reset_case_agents() — reassigns stuck issues +
clears error status in Paperclip DB + calls reset_agent_session
- agent_platform_port.py: exports pc_reset_case_agents (G12 gate)
- app.py: POST /api/cases/{case_number}/agents/reset endpoint
- agents.ts: useResetCaseAgents mutation hook + AgentResetResult type
- agent-status-widget.tsx: 'אפס' button (shown only when error agents
exist) with Dialog confirmation + loading state + toast feedback
Invariants: G12 (Paperclip only via port), G2 (no parallel path —
uses existing reset_agent_session + pc_get_case_issues).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Full code review of the case-detail page (14 components) surfaced these,
all fixed here:
Logic bugs
- case-edit-dialog: form.reset ran on the 5s useCase refetch while the dialog
was open, clobbering in-progress edits. Now resets only on open→true.
- status-changer: `selected` never synced to async/external `currentStatus`
(stale dropdown). Reworked to track currentStatus until an explicit pick;
resets to tracking after save.
- decision-blocks-panel: `block.content`/`word_count` accessed without null
guards (endpoint has no response model) → potential render crash. Coerced
with `?? ""` / `?? 0`. `STATUS_LABELS[status]` now falls back to the raw
status instead of rendering literal "undefined".
- document-type-editor: `await mutateAsync()` in async click handlers without
try/catch → unhandled promise rejection. Wrapped (errors still surface via
isError).
Resilience / hygiene
- page.tsx: a transient 5xx on the background poll flipped the WHOLE page to
the error card and discarded loaded data. Now gated on `!data`, plus a
"נסה שוב" retry.
- cases.ts useUpdateCase: invalidated casesKeys.all, which re-invalidated the
detail it had just optimistically patched. Scoped to the list prefix.
RTL correctness (logical properties)
- agent-status-widget `mr-auto`→`ms-auto`; agent-activity-feed `mr-auto`→
`me-auto`, icon `ml-1/ml-2`→`me-1/me-2`, required `*` `mr-1`→`ms-1`;
document-type-editor list `pr-4`→`ps-4`.
Minors
- drafts-panel: `<a href>`→`next/link` (operations + citation links) for SPA
nav. agent-activity-feed: issueMap memoized; comment Textarea aria-label.
upload-sheet: `unknown` status no longer shown as green success (neutral
icon + "רענן לאישור"). citations.ts: case_name typed `string | null`.
Design gate: visual-touching items (RTL gap side, retry button, neutral
upload icon) were chair-authorized via the reviewed-findings approval ("fix
all"); none alter an approved page layout — they are correctness fixes.
tsc clean; eslint clean (1 pre-existing form.watch warning, untouched line).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Implements the Claude-Design-approved inline status banners for the
"חשב טיעונים" panel (mockup 25-legal-arguments-panel): the four endpoint
states (queued / exists / no_claims / skipped) now render as tone-coded
banners below the header instead of a transient toast. Toast is kept only
for hard transport errors.
Also fixes a pre-existing double-card bug found while reviewing the page:
the page's "arguments" tab already wraps the panel in <Card><CardContent>
(page.tsx:151-155), yet LegalArgumentsPanel rendered its OWN <Card> too —
unlike its sibling tab panels (DecisionBlocksPanel/DraftsPanel/
AgentActivityFeed) which render a plain <div>. The panel now matches that
convention (single card, no double border/padding), consistent with the
approved single-card mockup.
Design gate: card 25 approved by חיים. tsc + eslint clean.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The /aggregate-arguments endpoint ran an in-container BackgroundTask that
called claude_session (the local `claude` CLI) — which does not exist in the
FastAPI container. The button silently produced nothing, and on `force` it
destructively DELETEd existing arguments *before* the doomed LLM call.
Replace the inline task with the established delegation pattern used by
"חלץ עובדות שמאיות" (extract-appraiser-facts): a cheap in-container DB
pre-check (no_claims / exists), then a Paperclip wakeup of the company's
legal-analyst, which runs mcp__legal-ai__aggregate_claims_to_arguments
locally (where the CLI lives) and reports back. `force` now runs locally too,
so delete+recompute are atomic on the host — no more destructive failure.
Frontend: AggregateArgumentsResult becomes a discriminated union
(queued | no_claims | exists | skipped) and the toast is status-accurate
instead of the misleading fixed "refresh in a minute".
Invariants: G12 (Paperclip touch confined to paperclip_client behind the
agent_platform_port), G2 (replaces the broken path, no parallel capability),
engineering §6 (explicit statuses, no silent swallow).
UI change is logic/toast only (no visual-layout change) — within the
Claude-Design-gate bug-fix exemption. Richer inline status panels deferred
to the gate.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The hearing-date/updated/sync metadata dl dropped below the title (taking rows and
pushing the band down) when the appellants/respondents line was long, because the
outer row used flex-wrap. Fix:
- Drop flex-wrap on the title↔metadata row (and flex-1 on the title block) so the
metadata dl stays parallel to the H1; the title block shrinks instead.
- Clamp the parties line (and the subject fallback) to 2 lines with line-clamp-2 +
title tooltip, so a long party list no longer grows the band.
Chair-directed layout fix; existing components, no new design.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Chair-directed navigation/layout fixes (existing components, no new design):
1. "פתח עורך החלטה" moved into the case-page band actions (right after "העלאת
מסמכים") so it's reachable from EVERY tab, not only סקירה. Removed the now-
redundant full-width CTA from the overview tab.
2. Prominent "→ חזרה לדף התיק" back-link added to the /compose header (the
breadcrumb link was too subtle).
3. Home cases table: rail trimmed 360→280px and the title cell made min-w-0 so the
table gets the width it needs and no longer shows a horizontal scrollbar.
No backend / API change. The larger NEW citation-verification panel stays gated
behind Claude Design (preview 24-citation-verification already pushed).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- הסרת כרטיס "סקירת התיק" הדל (3 אלמנטים בלבד)
- כפתור "התחל תהליך" עלה לפס-הפעולות בראש הדף (ליד "העלאת מסמכים")
- כרטיס חדש AgentActivityPreview: 4 הissues האחרונים + קישור לטאב מלא
- "תוצאה צפויה" ירדה לשורת מטא-דאטה בראיל הסטטוס (מוצגת רק אם נקבעה)
- תיקון באג: expected_outcome לא נשמר — guard if expected_outcome: הוחלף ב-is not None
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
אחרי העלאת החלטה סופית והרצת שני הפייפליינים האוטומטיים (למידת-קול,
חילוץ/אימות-הלכות), התיק לא הציג אם כל תהליך בוצע/הצליח/למה-נכשל. במיוחד
תקלת chair_name ריק (2026-06-12) שמפילה בשקט את העתק-ה-case_law → חילוץ-הלכות
לא מתחיל בכלל, בלי שזה גלוי. כעת מוצגות שתי אינדיקציות ליד כפתורי-ההרצה.
Backend (גזירה ממקור-יחיד, ללא מסלול-מעקב מקביל):
- SCHEMA_V36: draft_final_pairs.learning_run (JSONB) — שדה-תיעוד על פנקס-ההתאמה
(INV-LRN4), חותם את תוצאת-הריצה של פייפליין-הלמידה (succeeded/failed+סיבה+at).
- set_learning_run_outcome() — חיתום הצלחה/כישלון על ה-pair האחרון.
- case_learning_status() — גזירה read-only מ-draft_final_pairs/style_corpus/
decision_lessons/case_law/halachot: בוצע? הצליח? למה-לא? כמה הלכות חולצו.
- final_learning_pipeline.py — חותם outcome בהצלחה וב-except (surfaced, לא בלוע).
- חשיפה: case_get מוסיף learning_status (→MCP + /api/cases/{case}/details) +
endpoint ייעודי GET /api/cases/{case}/learning-status (אותה פונקציה — בלי כפילות).
UI (אושר דרך שער-העיצוב Claude Design — כרטיס 21-final-learning-status):
- useCaseLearningStatus (api/learning.ts) — hook + polling עדין בזמן in-flight.
- LearningStatusBadges — 2 שורות (למידת-קול / חילוץ-הלכות) עם badge + תת-שורה
(מס' לקחים · רישום-קורפוס / מס' הלכות + פירוק אושרו/ממתינות/נדחו / סיבת-כישלון).
- שילוב ב-drafts-panel תחת "החלטה סופית של היו״ר" + אינוולידציה בכפתורי-ההרצה.
אומת מול ה-DB החי: הצליח+5 הלכות (8174-12-24) · נכנס-אך-pending (1200-12-25) ·
לא-נכנס-לקורפוס (8125-09-24) · round-trip חיתום-כישלון. tsc/eslint נקיים.
Invariants: G1 (נרמול-במקור — גזירה, לא טלאי), G2 (אין מסלול מקביל — שדה על
הפנקס הקיים + exposer יחיד), INV-LRN4 (פנקס-ההתאמה), INV-IA1 (מקור-אמת יחיד).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
ממצא: התוכנית המקורית (namespace ל-paperclip.* ב-types.ts) בלתי-ישימה — types.ts
נוצר-אוטומטית מ-OpenAPI ("Do not make direct changes"); הפניות-Paperclip שם רק משקפות
את ה-API של ה-backend, ונשלטות ע"י מודלי-ה-Pydantic, לא ע"י הפרונט. הפרונט אינו
שכבת-אינטליגנציה — הפניות-Paperclip בו הן הצגת-נתוני-פלטפורמה (activity feed, קישור
לדאשבורד, סטטוס-ארכוב) או UI-ניהול מוצהר (paperclip-tab/agents-tab) — כולן shell-adjacent
לגיטימי תחת G12.
הבעיה האמיתית-והישימה: התנגשות-שם — `PaperclipAgent` הוגדר פעמיים עם shapes שונים
(config ב-paperclip-agents.ts מול activity ב-agents.ts). פוצל: ה-activity-DTO →
`PaperclipAgentStatus`; ה-config שומר `PaperclipAgent`. + הערת-כותרת שמסמנת את agents.ts
כמודול הצגת-פלטפורמה מוצהר.
מזין את R4 (#113): leak-guard חייב להחריג קבצים-נוצרים (types.ts) ולא לכלול את הפרונט
בהיקף שכבת-האינטליגנציה המוגנת.
אימות: tsc --noEmit נקי; eslint נקי על הקבצים ששונו.
Invariants: G12 (גבול-פלטפורמה מוצהר בפרונט), G2 (הסרת שם-טיפוס כפול-משמעות).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
הקישור טיעון↔פרופוזיציות כבר נשמר ב-DB (legal_argument_propositions),
אך ה-UI הציג רק את המספר. מעשיר את get_legal_arguments באותו round-trip
(JOIN ל-claims) להחזיר supporting_propositions = {id, text, source_document},
ועוטף את שורת "מסתמך על N פרופוזיציות" ב-Popover שמציג את הטענות הגולמיות
verbatim עם מקור. שקיפות ועקיבוּת מהטיעון המאוגד חזרה לטענות-המקור.
- supporting_claims נשאר id-only (תאימות לאחור: מונה, צרכני MCP)
- supporting_propositions שדה חדש אופציונלי; fallback לטקסט סטטי כשחסר
- אין מסלול מקביל (G2) — העשרה של אותו endpoint; נרמול-במקור (G1)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
סוגר את הפער שלולאת-צמיחת-הקורפוס (07-learning §1.3) הוגדרה אך לא חווטה: מסלול
/final/upload הכניס רק לקורפוס-הסגנון, וההכנסה ל-case_law הייתה best-effort
שקטה שנכשלה כש-chair_name ריק.
web/app.py — /api/cases/{case}/final/upload עכשיו, סינכרונית:
- קובע chair_name דטרמיניסטית (תיק → ברירת-מחדל-ועדה לפי prefix; לעולם לא ריק →
אילוץ case_law_internal_chair_check תמיד מסופק). לא נשען על חילוץ-LLM —
להחלטות שלנו היו"ר ידוע.
- מכניס את ההחלטה ל-case_law כ-internal_committee (תמיד, לא best-effort) →
ברת-ציטוט בהחלטות עתידיות. מטה-דאטה נוסף מועשר אסינכרונית (Gemini).
- מחלץ את הציטוטים שההחלטה מצטטת (extract_internal_citations), ו**מסמן
אוטומטית** כל ציטוט שאינו בספרייה כ-missing_precedent (open) — dedup מול קיימים.
- התוצאה מוחזרת ב-response (enrolled/linked/missing_flagged) — לא נבלעת בשקט.
הציטוטים-המקושרים מזינים את לולאת-ה-corroboration (X11) — תוקן הניתוק שבו
החלטות שלנו לא היו ב-case_law ולכן לא חיזקו הלכות.
web-ui — toast מציג "נוספה לספרייה · N ציטוטים · M חסרים סומנו".
ספ: 07-learning §0.6 עודכן. אומת ידנית על בל"מ 8126-03-25 (15 קושרו / 6 סומנו).
Invariants: INV-LRN4, X11; G2 (יכולת חסרה, לא מקבילה); feedback_silent_swallow
(כשל-הכנסה צף, לא נבלע); DM7 (סמכות נגזרת).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
מוסיף מסלול ייעודי לקליטת ההחלטה החתומה של היו"ר, ומפעיל אותו דרך שני
שלבים אוטומטיים מדורגים עם פאנלי-סוכנים (אוטו-אישור + אסקלציה ליו"ר).
Backend (web/):
- POST /api/cases/{case}/final/upload — קליטת final חיצוני: שמירה קנונית
(סופי-{case}.docx + עותק קורפוס-סגנון תחת case_number מלא כדי שבל"מ לא
יתנגש עם ערר באותו מספר), פתיחת draft_final_pairs (final_received). לא נוגע
ב-active_draft ולא מריץ retrofit (נבדל מ-exports/upload ו-mark-final → לא G2).
- POST .../final/run-learning + .../final/run-halacha — שלבים מדורגים שמעירים
worker מקומי (claude/DeepSeek/Gemini מקומיים בלבד) דרך הרחבת
wake_curator_for_final עם param task=learning|halacha.
פאנל-סגנון חדש (scripts/style_lesson_panel.py): שני שופטים (DeepSeek+Gemini)
על-גבי דיסטילציית-ה-Opus; הסכמה 2/2-keep → decision_lesson
(source=panel:deepseek+gemini); substance מדולג (INV-LRN5); הפיך + גיבוי CSV.
פאנל-הלכות: docstring/SCRIPTS.md עודכנו (--apply מחווט).
Frontend (web-ui/): כפתור "העלאת החלטה סופית של היו"ר" + שני כפתורים מדורגים
"הרץ למידת-קול"/"הרץ אימות-הלכות" ב-drafts-panel; כל התוויות בעברית
(badge מקור-לקח: "פאנל: דיפסיק+גמיני", "הרמס (סקירה)"...).
Spec: docs/spec/07-learning.md §0.6. Invariants: INV-LRN1/LRN4/LRN5, G10
(שער-יו"ר ידני להטמעה ל-SKILL.md/lessons.md — הפאנלים יוצרים הצעות בלבד);
G2 (מסלול-סופי הוא יכולת חסרה, לא מסלול-מקביל).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
סוגר את לולאת פידבק-יו"ר→ידע-סוכנים. עד כה resolve רק עדכן את ה-DB; עכשיו
לחיצה ב-/feedback מעירה את ה-CEO שמקפל את הלקח לקובץ לפי הקטגוריה.
- paperclip_client.py: wake_ceo_for_feedback_fold() — יוצר issue ב-Paperclip
עם הלקח + rubric ניתוב (style→SKILL.md, wrong_structure→block-schema,
אחר→lessons.md), מעיר CEO. משכפל את דפוס wake_for_precedent_extraction
- db.py: get_chair_feedback(id) — שליפת הערה בודדת עם case_number/appeal_type
- app.py: resolve endpoint מקבל fold (ברירת מחדל true); BackgroundTask
fire-and-forget; guard — רק עם lesson_extracted. מחזיר fold_queued
- legal-ceo.md: dispatch ל-feedback_fold_ + סעיף "קיפול הערת יו"ר" עם rubric
- frontend: useResolveFeedback מקבל fold; /feedback שולח fold=true עם toast;
drafts-panel שולח fold=false (bookkeeping per-case, בלי קיפול כפול)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Adds a new "ההחלטה" tab to the case detail page showing all 12 decision
blocks with rendered markdown content and inline editing that saves back
to the DB via two new FastAPI endpoints.
Backend (web/app.py):
- GET /api/cases/{n}/decision-blocks — returns all 12 blocks (empty
ones included) merged from BLOCK_CONFIG + decision_blocks table.
Exposes source_of_truth ("docx"|"blocks") and active_draft_path.
- PUT /api/cases/{n}/decision-blocks/{block_id} — inline save via
block_writer.save_block_content; warns (does not block) when an
active DOCX draft exists.
Frontend:
- src/lib/api/decision-blocks.ts — typed hooks (useDecisionBlocks,
useSaveBlock) following the cases.ts hand-written-module pattern.
- src/components/cases/decision-blocks-panel.tsx — accordion of 12
blocks; view mode renders Markdown component; edit mode is a textarea
with on-blur save (derived from ChairEditor pattern, setState-during-
render for re-sync to avoid effect cascade).
- BLOCK_LABELS in feedback.ts extended from 7 → 12 blocks.
- cases/[caseNumber]/page.tsx — new "ההחלטה" tab wired to the panel.
No DB migration required — decision_blocks + active_draft_path exist.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The "חלץ עובדות שמאיות" UI button hit POST /api/cases/{n}/extract-appraiser-facts
which called appraiser_facts_extractor inline — that shells out to the local
`claude` CLI, which is absent in the Coolify container, so every doc errored,
the per-doc try/except swallowed it, and the response was "completed, 0 facts".
Refactored the endpoint to wake the legal-analyst of the correct company via
Paperclip (same pattern as wake_curator_for_final), and surface
extraction_failed instead of "completed" when every doc errored.
After the proceeding_type field landed, users started flipping cases
to בל"מ via the edit dialog. But the case-header badge + cases-table
filter were still gated on isBlamSubtype(appeal_subtype), so the badge
didn't appear when only the proceeding_type changed. Now the badge
shows when either proceeding_type === 'בל"מ' OR appeal_subtype is an
extension_request_* variant — the legacy path stays so existing rows
that never got a proceeding_type still render correctly.
Also regen types.ts from prod (proceeding_type now in OpenAPI schema)
and register the one-shot process_pending_blam.py script.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Same case_number can exist as both a regular appeal (ערר) and an
extension-of-time request (בל"מ), and we were inferring the difference
from appeal_subtype prefixes — fragile, and case-number lookups
weren't disambiguated. Now stored as a first-class field on both
case_law (corpus) and cases (live cases), with partial unique indexes
on (case_number, proceeding_type).
- SCHEMA_V15: column + CHECK constraints + backfill from
appeal_subtype LIKE 'extension_request_%' + partial unique indexes
replace the old global UNIQUE(case_number).
- derive_proceeding_type() centralizes the inference rule
(extension_request_* → בל"מ; subject regex fallback; default ערר).
- Metadata extractor prompt asks Claude to populate the new field
explicitly; apply_to_record writes it for internal_committee rows.
- internal_decision_upload, case_create, case_update accept an
optional proceeding_type; FastAPI request models expose it.
- Wizard + edit dialog get a sided Select; case header renders the
resolved label (ערר / בל"מ).
- Uploaded the 2 staged בל"מ decisions on betterment levy:
8126/24 (סופר נוח, 13 chunks), 8047/23 (הרנון, 48 chunks).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Four parallel sub-agents closed the remaining critical gaps from the
26/05 Stage A/B sprint. Each block independently tested; aggregated here.
## #30/#31 finalizers (sub-agent A)
* Auto-derive practice_area in case_create from case_number prefix
(1xxx→rishuy_uvniya, 8xxx→betterment_levy, 9xxx→compensation_197);
default for CaseCreateRequest is now "" (the DB constraint catches
any stray "appeals_committee").
* practice_area.py: derive_subtype now handles axis-B domain values
(rishuy_uvniya/betterment_levy/compensation_197) without parsing the
case number; new helper derive_domain_practice_area().
* Halacha re-extraction verified unnecessary — all 6 reclassified
records already had is_binding=false and approved halachot.
* Regression tests: 6 cases in tests/test_corpus_constraints.py
covering practice_area enum, internal-committee chair/district,
external-upload arar prefix, MCP guard.
* UI: district input → Select dropdown (7 districts) in
precedent-edit-sheet.tsx, preserving legacy free-text values.
## #37 בל"מ subtypes (sub-agent B)
* 3 new appeal_subtypes: extension_request_{building_permit,
betterment_levy,compensation}. APPEALS_COMMITTEE_SUBTYPES extended,
SUBTYPES_BY_AREA mappings added.
* New helpers: is_blam_subject(), is_blam_subtype(),
derive_subtype_with_blam(case_number, subject, practice_area).
case_create now uses it to auto-detect "בקשה להארכת מועד" subjects.
* 3 methodology templates under docs/methodology/extension-request-*.md.
* paperclip_client.py mapping updated for the 3 new subtypes
(extension_request_building_permit→CMP, the other two→CMPA).
* Frontend: bilingual "בל"מ" badge + filter dropdown on cases list +
detail header; appeal-type-bars collapseBlam() merges בל"מ into its
parent domain for aggregate bars.
* Wizard auto-detects בל"מ from subject during case creation.
* 3 Berlinger cases (1017/1018/1019-03-26) migrated to
appeal_subtype=extension_request_building_permit via psql.
## #35 missing_precedents feature (sub-agent C)
* Schema V13: missing_precedents table (citation, case_id, party,
legal_topic, status, linked_case_law_id, claim_quote, ...) +
FK constraints + 3 indexes. Applied via psql + idempotent migration.
* 6 db.py service functions, 3 MCP tools, 6 FastAPI endpoints
(POST/GET/PATCH/DELETE/upload — upload routes by citation prefix
to ingest_internal_decision or ingest_precedent).
* Next.js page /missing-precedents with 5 status tabs + filters +
sidebar badge counter + detail drawer with metadata edit + smart
upload form that switches fields per committee/court.
* Bootstrap: 7 rows imported from the JSON file
(3 citations × cases, all status=closed with linked_case_law_id).
* legal-researcher.md: new §2ב.5 with missing_precedent_create
usage + dedup semantics + tool grant.
## #36 legal_arguments aggregation (sub-agent D)
* Schema V14: legal_arguments + legal_argument_propositions M:M.
Applied via psql.
* New service argument_aggregator.py with two functions —
aggregate_claims_to_arguments() (Claude CLI / claude_session) and
get_legal_arguments(). Graceful llm_unavailable handling when CLI
is missing (containers).
* 2 MCP tools + 2 API endpoints (POST .../aggregate-arguments as
BackgroundTask, GET .../legal-arguments).
* Frontend: shadcn Accordion + new legal-arguments-panel.tsx with
hierarchical (party → priority badge → arguments) display, "טיעונים"
tab on the case page, "חשב/חשב מחדש" buttons.
* scripts/backfill_legal_arguments.py + SCRIPTS.md entry — dry-run
found 8 candidate cases including 1017/1018/1019.
## Open follow-ups (intentionally deferred)
* npm run api:types in web-ui (CLAUDE.md flow) — recommended before
the next UI commit; not required for backend deployment.
* Run backfill_legal_arguments.py --apply once the container picks up
the new aggregator service.
* webhook on missing-precedents upload-close to Paperclip (optional).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Wrap date.fromisoformat() in try/except in case_update tool — prevents
unhandled ValueError from surfacing as 500; FastAPI now catches it as 422
- Add DialogDescription (sr-only) to 5 dialogs missing aria-describedby:
documents-panel preview + delete, drafts-panel delete + feedback, link-related-dialog
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Fix keyboard navigation bug: React was reusing the submit button DOM element
when transitioning "הבא" → "צור תיק", retaining focus and causing Enter to
auto-submit step 3. Added key props to force element replacement.
- CaseEditDialog now covers all wizard fields: appellants, respondents,
property_address, permit_number (in addition to existing title, subject,
hearing_date, expected_outcome, notes).
- When case title changes, Paperclip project name is updated in background
via new update_project_name() in paperclip_client.py.
- Extended CaseUpdateRequest, case_update MCP tool, and caseUpdateSchema
to carry the new fields end-to-end.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Surface issue_thread_interactions (ask_user_questions / request_confirmation /
suggest_tasks) directly inside legal-ai's case detail feed so the user can
answer agent prompts without switching to Paperclip's UI.
Backend (FastAPI):
- paperclip_client.py: 4 new helpers — get_issue_interactions (DB),
respond_to_interaction / accept_interaction / reject_interaction (REST).
- app.py: extends GET /api/cases/{case_number}/agents to include
`interactions`, and adds POST /api/cases/{case_number}/agents/interaction-response
routing to /respond, /accept, /reject in Paperclip.
- paperclip_client.py: also pulls existing httpx calls onto the centralized
pc_request helper (paperclip_api.py) for consistent auth + run-id headers.
Frontend (web-ui, Next.js 16 + TanStack Query):
- agents.ts: Interaction / InteractionPayload / InteractionStatus types,
useSubmitInteraction mutation hook (invalidates the activity query).
- agent-activity-feed.tsx: InteractionCard renders radio (single) /
checkbox (multi) for ask_user_questions, accept/reject + reason for
request_confirmation, task selection for suggest_tasks. Resolved
interactions show a read-only summary. Cards are interleaved with
comments by created_at, so the feed reads chronologically.
Paperclip auto-wakes the issue assignee on a successful response
(queueResolvedInteractionContinuationWakeup) — no explicit wakeup needed.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Reduce vertical padding, number font size, and inter-element gaps so
the four counters take less vertical real estate. Width unchanged.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The list's scroll container had only overflow-y:auto, which CSS computes
overflow-x to auto too. Combined with the row's -mx-2 hover-background
extension, this surfaced an unwanted horizontal scrollbar.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Backend (cases listing)
- /api/cases: also return updated_at, created_at, practice_area,
appeal_subtype, subject. The detail-mode response was previously
dropping these even though db.list_cases reads them, leaving the
UI's "תחום" and "עודכן" columns blank.
Frontend
- Split the home table into two: רישוי (1xxx) and היטל השבחה ופיצויים
(8xxx + 9xxx), bucketing on appeal_subtype with a case-number-prefix
fallback. The "תחום" column is now redundant and removed.
- New AppealTypeBars chart in the right rail next to the existing
status donut.
- Donut: switch to a vertical layout (donut on top, legend below in a
3-col grid) so labels like "חדש / בעיבוד" no longer wrap inside the
320px sidebar; counts now align in a tabular column.
- CasesTable accepts emptyText/searchPlaceholder so each split table
has its own copy.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The auto-creation in case_create had two failure modes that combined to
make repos silently missing: a stale GITEA_TOKEN returning 401, and the
outer try/except in case_create that swallowed every exception with a
bare pass. Result: cases like 8174-24 ended up with a local git repo and
Paperclip project but no Gitea repo, with no signal anywhere.
_setup_gitea_remote now returns {ok, url, error} and never raises; the
result is attached to the case JSON and the FastAPI endpoint logs a
warning when ok=false. The UI gets a "צור ריפו ב-Gitea" button on the
case header that appears only when the repo or remote is missing.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The AgentActivityFeed showed a spinner with "הסוכנים התחילו לעבוד,
ממתין לדיווח ראשון..." whenever the case had any issues but no
comments — including cases where all issues had ended in 'done' or
'cancelled' (like 1130-25 after archive). The widget mistook a
finished case for an in-flight workflow.
Now compute hasActiveIssue = some(issues, status !== done && cancelled)
and pick the message accordingly: spinner only while there's still
real work; otherwise a quiet "אין משימות פעילות בתיק. כל המשימות
הסתיימו או בוטלו." with the static MessageSquare icon.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
When a case is archived, the legal-ai UI's AgentStatusWidget kept showing
"agents started working, waiting for first report" because related
Paperclip issues remained in 'todo' / 'in_progress' status. Concrete
example: case 1130-25 had two open issues (CMP-15 ניתוח תכנוני, CMP-21
כתיבת החלטה) that lingered after the case was finalized; 1194-25 had
two more (CMP-37, CMP-44).
Extended pc_archive_project to also UPDATE issues SET status='cancelled',
cancelled_at=now() WHERE project_id matches AND status IN
('backlog','todo','in_progress','blocked','in_review'). Returns the list
of cancelled issues so the toast can announce the count.
Updated cases.ts ArchiveResult.paperclip.issues_cancelled type and the
toast message in case-archive-action to surface "(N משימות פתוחות בוטלו)"
when relevant.
Restore is intentionally unchanged — we don't auto-recreate cancelled
issues; if work needs to resume, a fresh issue should be created.
Stale issues for 1130-25 / 1194-25 cancelled directly in DB as a one-off
cleanup (CMP-15, CMP-21, CMP-37, CMP-44).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds a comprehensive archive flow for closed cases — separate /archive
screen in the UI, archive/restore actions on the case detail page, and
automatic two-way sync with Paperclip.
Backend (web/app.py + mcp-server/services/db.py):
- New SCHEMA_V6 migration: cases.archived_at TIMESTAMPTZ + partial index
- list_cases gains include_archived/archived_only flags; default excludes
archived rows so the main /api/cases list hides closed cases
- archive_case / restore_case helpers in db.py
- POST /api/cases/{n}/archive sets archived_at and calls
pc_archive_project (sets Paperclip projects.archived_at via direct DB)
- POST /api/cases/{n}/restore clears archived_at and calls
pc_restore_project (clears Paperclip archived_at)
- archive_project / restore_project in paperclip_client.py — name-based
match consistent with create_project's lookup
Frontend (web-ui):
- cases.ts: scope param ("active"|"archived"|"all") on useCases;
useArchiveCase / useRestoreCase mutations
- /archive page (new): table of archived cases with restore button +
search, sort, empty state matching the editorial aesthetic of /
- case-archive-action.tsx: button on case detail header. Active case →
confirm dialog → archive. Archived case → restore (no confirm).
Toast announces both legal-ai and Paperclip outcomes (synced, not
found in pc, error)
- case-header shows "בארכיון" badge when archived_at is set
- Nav: ארכיון link added to AppShell after בית
Tested end-to-end against the live DB:
- 1130-25 archive → list_cases(include_archived=False) excludes it,
list_cases(archived_only=True) includes it, restore reverses
- pc archive/restore on 1194-25 verified via direct DB lookup
- TypeScript compiles clean
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The "על כן" pattern for block-yod-bet was too greedy and matched mid-discussion
transitional sentences (e.g. "על כן, במקום בו..."), which caused forward-scan
to skip block-yod-alef ("סוף דבר") via the pointer advance.
Tightened to require an operative subject (אנו / הערר / הוועדה / ועדת הערר)
so terminal "על כן, אנו מחליטים" still matches but mid-block transitions don't.
Added structural_fallback for cover blocks (alef/bet/gimel/dalet) — these are
template metadata not present in user-edited DOCX bodies. Inject zero-content
anchors so apply_user_edit can still target them later. The frontend toast
distinguishes real content gaps from fallback anchors.
Also expanded heading patterns based on training corpus inspection:
- block-vav: על המקרקעין חלות / במצב התכנוני / התכניות החלות
- block-zayin: טענות העוררת
- block-chet: עיקר תגובת המשיב
- block-tet: הדיון בוועדת הערר
For case 1130-25, this raises detection from 6/12 to 11/12 blocks — only
block-yod-bet remains missing (Daphna's edit ends at "סוף דבר" + numbered
ruling, no terminal "ההחלטה" or "על כן אנו מחליטים" paragraph).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Extraction is expensive (multi-minute LLM calls) and runs across every
appraisal in the case at once, so we don't kick it off silently on every
tag save. The chair tags the appraisals, then runs extraction once when
they're ready.
- New POST /api/cases/{n}/extract-appraiser-facts endpoint returns the
extractor's summary as-is: status=completed with fact counts and
conflicts, or status=sides_missing with the list of still-untagged
appraisal docs.
- DocumentTypeEditor now has a two-phase popover. After a successful
save on an appraisal doc, the body switches to a confirmation view
with a "חלץ עובדות שמאיות עכשיו" button. The result (completed /
sides_missing / no_appraisals / error) renders in the same popover
so the chair sees exactly which appraisals still need tagging
without closing and reopening anything.
- useExtractAppraiserFacts React-Query mutation invalidates the case
detail on success so downstream views (conflict rendering in
block-tet context) pick up the new facts.
Until now changing a document's doc_type required a manual SQL update.
Adds an inline editor on the document badge so the chair can retag
without leaving the case page, and threads an appraiser_side tag
(committee / appellant / deciding) through the appraisal pipeline so
betterment-levy cases — which usually have 2-3 appraisers — render
conflicts with the deciding appraiser's view marked as governing.
Backend
- New appraiser_facts.appraiser_side column (V5.1) populated from
documents.metadata.appraiser_side at extraction time.
- extract_appraiser_facts now returns status='sides_missing' with the
list of untagged appraisals instead of running with empty side
labels — chair must tag every appraisal first via the UI.
- Conflict detection orders entries committee → appellant → deciding so
the deciding appraiser appears last; block-tet's prompt instructs the
writer to phrase the deciding appraiser's view as the governing
factual finding ("ואולם, השמאי המכריע קבע...").
- New PATCH /api/cases/{n}/documents/{doc_id} (Pydantic model with
whitelist validation) and matching document_update MCP tool. Both
merge appraiser_side into metadata JSONB instead of touching the
schema.
UI
- New shared doc-types module exports the canonical 11 doc_type
options plus the 3 appraiser-side options; both upload-sheet and
the document badge now read from it instead of duplicating Hebrew
labels.
- New DocumentTypeEditor renders a Popover off the doc-type Badge
with two Selects. The save button stays disabled while doc_type is
appraisal but no side has been picked, mirroring the backend
enforcement so the user finds out before triggering extraction.
- usePatchDocument React-Query mutation invalidates the case detail
on success so the badge updates without a manual refresh.
The backend (app.py, documents.py, models.py) already maps appraisal→שומה
but the frontend DOC_TYPE_LABELS and upload DOC_TYPES dropdowns were
missing the entry, so appraisal documents rendered as the raw English
string instead of the Hebrew label.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>