feat(graph): navigation & UX — deep-link, depth, PNG, rich panel (PR D)
Final corpus-graph PR. Connects the graph to the chair's workflow and rounds out the Obsidian-grade interactions. Backend (web/graph_api.py): neighborhood depth cap 2 → 3 (still bounded by NODE_CAP_MAX). Frontend: - URL deep-link: /graph?focus=cl:<id> is read on mount and written on focus change (router.replace, scroll:false). GraphView wrapped in <Suspense> per Next 16's useSearchParams requirement. - "הצג בגרף" button on the precedent detail page → /graph?focus=cl:<id>. - Depth slider (1–3) in the focused overlay → useNodeNeighborhood(id, depth). - Export PNG: grabs the rendered <canvas> from the area ref → toDataURL → download; failures surface a toast (UI4). - Rich node panel: precedent nodes fetch headnote/summary via the existing usePrecedent hook (Skeleton while pending, error surfaced — UI4). - Edge-type legend (ציטוט / נושא-תחום / יומון) added under the node legend. Deferred (noted for a later pass): expand-in-place merge, search→camera-center. web-ui build + lint pass. Invariants: G2 (depth change is read-only), UI4 (PNG + detail errors surfaced, not swallowed). api:types post-deploy. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -12,7 +12,9 @@ import { ExternalLink, X } from "lucide-react";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Card, CardContent } from "@/components/ui/card";
|
||||
import { Skeleton } from "@/components/ui/skeleton";
|
||||
import type { GraphNode } from "@/lib/api/graph";
|
||||
import { usePrecedent } from "@/lib/api/precedent-library";
|
||||
|
||||
const TYPE_LABELS: Record<string, string> = {
|
||||
precedent: "פסיקה",
|
||||
@@ -54,6 +56,9 @@ export function GraphNodePanel({
|
||||
const isPrecedentLike = node.type === "precedent" || node.type === "halacha";
|
||||
const isGap = node.type === "gap";
|
||||
const isDigest = node.type === "digest";
|
||||
// Rich detail (summary/headnote) for precedents — reuses the library hook.
|
||||
const detailId = node.type === "precedent" ? node.case_law_id : null;
|
||||
const detail = usePrecedent(detailId);
|
||||
return (
|
||||
<Card className="bg-surface border-rule shadow-sm w-80 shrink-0 overflow-y-auto">
|
||||
<CardContent className="space-y-4 p-4">
|
||||
@@ -119,6 +124,25 @@ export function GraphNodePanel({
|
||||
)}
|
||||
</dl>
|
||||
|
||||
{detailId && (
|
||||
<div className="space-y-1.5 border-t border-rule pt-3">
|
||||
{detail.isPending ? (
|
||||
<>
|
||||
<Skeleton className="h-3 w-full" />
|
||||
<Skeleton className="h-3 w-4/5" />
|
||||
</>
|
||||
) : detail.error ? (
|
||||
<p className="text-ink-muted text-xs m-0">לא ניתן לטעון תקציר.</p>
|
||||
) : detail.data?.headnote || detail.data?.summary ? (
|
||||
<p className="text-ink text-sm leading-relaxed m-0">
|
||||
{detail.data.headnote || detail.data.summary}
|
||||
</p>
|
||||
) : (
|
||||
<p className="text-ink-muted text-xs m-0">אין תקציר.</p>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{isPrecedentLike && node.case_law_id && (
|
||||
<Button asChild variant="outline" className="w-full">
|
||||
<Link href={`/precedents/${node.case_law_id}`}>
|
||||
|
||||
Reference in New Issue
Block a user