refactor(positions): קיפול "עמדות וטענות", איזון שורה תחתונה, שם-כרטיס מדויק (#226)
All checks were successful
G12 Leak-Guard / leak-guard (pull_request) Successful in 4s
Lint — undefined names / undefined-names (pull_request) Successful in 10s

לפי הנחיות חיים על טאב "טיעונים ועמדות":
- "עמדות וטענות" נעטף באקורדיון single/collapsible סגור-כברירת-מחדל, כמו
  "טיעונים מאוגדים לפי צד" ו"מה קרה בדיון" — הטאב נפתח כ-3 פסים מקופלים.
  התיאור עבר לטריגר; הוסר ה-lead הכפול מהרכיב.
- שורה תחתונה מאוזנת: "פסיקה מצורפת" ו"ייצוא ועדכון הניתוח" באותו גובה
  (items-stretch + h-full על שני הכרטיסים).
- כותרת הכרטיס "השלמה והעברה" → "ייצוא ועדכון הניתוח" (שם מדויק — הכרטיס
  מייצא/מעדכן/מוריד את analysis-and-research.md, לא "משלים ומעביר").
- הוסרו שתי שורות-הסטטוס "הרץ למידת-קול" + "הרץ אימות-הלכות" (post-final;
  קיימות בטאב "טיוטות והערות" — הסרת-כפילות G2). נוקו helpers+import לא-משומשים.

עיצוב: מוקאפ 28-case-positions-collapsed-balanced אושר ע"י חיים דרך שער Claude Design (X17).
tsc + eslint ירוקים. תואם INV-IA1 (דפוס-אח מאושר).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-06 09:35:12 +00:00
parent 538d80f6ec
commit 972935ceb5
2 changed files with 34 additions and 69 deletions

View File

@@ -157,11 +157,28 @@ export default function CaseDetailPage({
issues, with the aggregated by-party arguments collapsible below issues, with the aggregated by-party arguments collapsible below
(merged from the deleted /compose editor, mockup 18f). */} (merged from the deleted /compose editor, mockup 18f). */}
<TabsContent value="arguments" className="mt-0 space-y-4"> <TabsContent value="arguments" className="mt-0 space-y-4">
<Card className="bg-surface border-rule shadow-sm"> {/* עמדות וטענות — collapsed by default (chair preference #226),
<CardContent className="px-6 py-5"> mirroring the two accordions below. */}
<PositionsPanel caseNumber={caseNumber} /> <Accordion type="single" collapsible>
</CardContent> <AccordionItem
</Card> value="positions"
className="overflow-hidden rounded-xl border border-rule bg-surface shadow-sm"
>
<AccordionTrigger className="px-6 py-4 hover:no-underline">
<span className="flex flex-1 flex-col items-start">
<span className="text-navy text-[0.95rem] font-bold">
עמדות וטענות
</span>
<span className="text-ink-muted text-[0.78rem]">
סוגיות-המחלוקת ועמדות הצדדים · עורך עמדת-היו״ר מזין את בלוק י׳
</span>
</span>
</AccordionTrigger>
<AccordionContent className="px-6 pb-5 pt-0">
<PositionsPanel caseNumber={caseNumber} />
</AccordionContent>
</AccordionItem>
</Accordion>
<Accordion type="single" collapsible> <Accordion type="single" collapsible>
<AccordionItem <AccordionItem
value="byparty" value="byparty"

View File

@@ -8,10 +8,6 @@ import { SubsectionCard } from "@/components/compose/subsection-card";
import { PrecedentsSection } from "@/components/compose/precedents-section"; import { PrecedentsSection } from "@/components/compose/precedents-section";
import { Markdown } from "@/components/ui/markdown"; import { Markdown } from "@/components/ui/markdown";
import { useCase } from "@/lib/api/cases"; import { useCase } from "@/lib/api/cases";
import {
useCaseLearningStatus,
type CaseLearningStatus,
} from "@/lib/api/learning";
import { useResearchAnalysis } from "@/lib/api/research"; import { useResearchAnalysis } from "@/lib/api/research";
import { useCasePrecedents } from "@/lib/api/precedents"; import { useCasePrecedents } from "@/lib/api/precedents";
@@ -24,45 +20,6 @@ import { useCasePrecedents } from "@/lib/api/precedents";
* verification moved to their own top-level tabs; /compose was deleted. * verification moved to their own top-level tabs; /compose was deleted.
*/ */
// ── Staged-pipeline indicator text — derived from the live learning-status,
// same source as the drafts-panel LearningStatusBadges. ──────────────────────
function voiceLearningText(s?: CaseLearningStatus): string {
if (!s?.final_uploaded) return "ממתין להעלאת הסופי";
const v = s.voice_learning;
if (v.outcome === "succeeded") {
const bits = [`${v.lessons_count} לקחים הופקו`];
if (v.lessons_proposed > 0) bits.push(`${v.lessons_proposed} הוצעו לאישור`);
return `✓ הושלם · ${bits.join(" · ")}`;
}
if (v.outcome === "failed") return v.error ? `✗ נכשל — ${v.error}` : "✗ נכשל";
return "ממתין להרצה";
}
function halachaExtractionText(s?: CaseLearningStatus): string {
if (!s?.final_uploaded) return "ממתין להעלאת הסופי";
const h = s.halacha_extraction;
if (!h.enrolled_in_corpus)
return h.not_enrolled_reason ?? "לא נכנס לקורפוס-הפסיקה";
switch (h.status) {
case "completed":
return `✓ הושלם · חולצו ${h.halachot_count} · ${h.approved} אושרו · ${h.rejected} נדחו`;
case "processing":
return "רץ עכשיו…";
case "pending":
case "busy":
return "בתור";
case "partial":
return `חלקי · חולצו ${h.halachot_count}`;
case "failed":
case "extraction_failed":
return "✗ נכשל";
case "no_chunks":
return "אין טקסט לחילוץ";
default:
return "ממתין להרצה";
}
}
function ProseSection({ title, content }: { title: string; content?: string }) { function ProseSection({ title, content }: { title: string; content?: string }) {
if (!content?.trim()) return null; if (!content?.trim()) return null;
return ( return (
@@ -75,7 +32,9 @@ function ProseSection({ title, content }: { title: string; content?: string }) {
); );
} }
// ── "השלמה והעברה" rail card — DOCX export, analysis upload/download (real) ── // ── "ייצוא ועדכון הניתוח" rail card — round-trips the analysis-and-research.md
// research analysis: export DOCX, upload an updated version, download the raw MD.
// (Post-final learning status lives on the drafts tab, not here — #226.) ──────
function FinishRail({ function FinishRail({
caseNumber, caseNumber,
hasAnalysis, hasAnalysis,
@@ -88,7 +47,6 @@ function FinishRail({
const fileRef = useRef<HTMLInputElement>(null); const fileRef = useRef<HTMLInputElement>(null);
const [uploading, setUploading] = useState(false); const [uploading, setUploading] = useState(false);
const [uploadMsg, setUploadMsg] = useState<{ ok: boolean; text: string } | null>(null); const [uploadMsg, setUploadMsg] = useState<{ ok: boolean; text: string } | null>(null);
const learning = useCaseLearningStatus(caseNumber);
async function handleUpload(file: File) { async function handleUpload(file: File) {
setUploading(true); setUploading(true);
@@ -119,9 +77,12 @@ function FinishRail({
} }
return ( return (
<Card className="bg-surface border-rule shadow-sm"> <Card className="bg-surface border-rule shadow-sm h-full">
<CardContent className="px-4 py-4"> <CardContent className="px-4 py-4">
<h3 className="text-navy text-[0.9rem] font-semibold mb-3">השלמה והעברה</h3> <h3 className="text-navy text-[0.9rem] font-semibold mb-1">ייצוא ועדכון הניתוח</h3>
<p className="text-[0.72rem] text-ink-muted mb-3">
פעולות על קובץ ניתוח-המחקר (analysis-and-research.md).
</p>
<input <input
ref={fileRef} ref={fileRef}
@@ -176,16 +137,6 @@ function FinishRail({
{uploadMsg.text} {uploadMsg.text}
</p> </p>
)} )}
{/* stage indicators — informational pointers, not actions */}
<div className="mt-3 space-y-0">
<div className="text-[0.78rem] text-ink-muted pt-2 border-t border-rule-soft">
<b className="text-navy">הרץ למידת-קול</b> {voiceLearningText(learning.data)}
</div>
<div className="text-[0.78rem] text-ink-muted pt-2 mt-2 border-t border-rule-soft">
<b className="text-navy">הרץ אימות-הלכות</b> {halachaExtractionText(learning.data)}
</div>
</div>
</CardContent> </CardContent>
</Card> </Card>
); );
@@ -214,10 +165,6 @@ export function PositionsPanel({ caseNumber }: { caseNumber: string }) {
return ( return (
<div className="space-y-4"> <div className="space-y-4">
<p className="text-ink-muted text-[0.78rem]">
סוגיות-המחלוקת ועמדות הצדדים. עורך עמדת-היו״ר נשמר אוטומטית ומזין את בלוק י׳ (דיון והכרעה).
</p>
{analysis.isPending ? ( {analysis.isPending ? (
<Card className="bg-surface border-rule shadow-sm"> <Card className="bg-surface border-rule shadow-sm">
<CardContent className="px-6 py-5 space-y-3"> <CardContent className="px-6 py-5 space-y-3">
@@ -322,9 +269,10 @@ export function PositionsPanel({ caseNumber }: { caseNumber: string }) {
</div> </div>
) : null} ) : null}
{/* case-level פסיקה מצורפת + finish/export gates (INV-IA3: not removed) */} {/* case-level פסיקה מצורפת + analysis export/update (INV-IA3: not removed).
<div className="grid gap-4 lg:grid-cols-2 items-start pt-2"> items-stretch keeps both cards the same height (#226). */}
<Card className="bg-surface border-rule shadow-sm"> <div className="grid gap-4 lg:grid-cols-2 items-stretch pt-2">
<Card className="bg-surface border-rule shadow-sm h-full">
<CardContent className="px-5 py-4"> <CardContent className="px-5 py-4">
<h3 className="text-navy text-[0.9rem] font-semibold mb-1">פסיקה מצורפת</h3> <h3 className="text-navy text-[0.9rem] font-semibold mb-1">פסיקה מצורפת</h3>
<p className="text-[0.72rem] text-ink-muted mb-3"> <p className="text-[0.72rem] text-ink-muted mb-3">