fix(case-page): resolve full-page review findings (bugs + RTL + resilience)
All checks were successful
G12 Leak-Guard / leak-guard (pull_request) Successful in 4s
Lint — undefined names / undefined-names (pull_request) Successful in 10s

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>
This commit is contained in:
2026-06-20 18:57:43 +00:00
parent 148b4b9bf6
commit 2b591f5018
11 changed files with 115 additions and 74 deletions

View File

@@ -25,14 +25,20 @@ export function StatusChanger({
caseNumber: string;
currentStatus?: CaseStatus;
}) {
const [selected, setSelected] = useState<CaseStatus | "">(currentStatus ?? "");
// `null` = untouched → the dropdown tracks the live `currentStatus` (which
// arrives async and changes on the 5s poll / external updates). Only an
// explicit pick overrides it, until save resets back to tracking.
const [picked, setPicked] = useState<CaseStatus | null>(null);
const mutate = useUpdateCase(caseNumber);
const effective: CaseStatus | "" = picked ?? currentStatus ?? "";
const canSave = Boolean(effective) && effective !== currentStatus;
const handleSave = async () => {
if (!selected || selected === currentStatus) return;
if (!canSave || !effective) return;
try {
await mutate.mutateAsync({ status: selected });
toast.success(`הסטטוס עודכן ל${STATUS_LABELS[selected]}`);
await mutate.mutateAsync({ status: effective });
toast.success(`הסטטוס עודכן ל${STATUS_LABELS[effective]}`);
setPicked(null);
} catch (e) {
toast.error(e instanceof Error ? e.message : "שגיאה בעדכון הסטטוס");
}
@@ -43,8 +49,8 @@ export function StatusChanger({
<label className="text-[0.72rem] text-ink-muted block">שינוי סטטוס ידני</label>
<div className="flex items-center gap-2">
<Select
value={selected || "__current__"}
onValueChange={(v) => setSelected(v === "__current__" ? "" : v as CaseStatus)}
value={effective || "__current__"}
onValueChange={(v) => setPicked(v === "__current__" ? null : v as CaseStatus)}
dir="rtl"
>
<SelectTrigger className="text-[0.75rem] h-8">
@@ -68,7 +74,7 @@ export function StatusChanger({
size="sm"
variant="outline"
className="h-8 text-[0.72rem] px-3 shrink-0"
disabled={!selected || selected === currentStatus || mutate.isPending}
disabled={!canSave || mutate.isPending}
onClick={handleSave}
>
{mutate.isPending ? "שומר…" : "עדכן"}