From 9154a1d81778fc8991c8d6d65358703d9a8e2e2f Mon Sep 17 00:00:00 2001 From: Chaim Date: Fri, 12 Jun 2026 11:46:41 +0000 Subject: [PATCH] feat(operations-ui): BURST toggle + deadline dialog for the halacha drain (#133) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Implements the chair-approved Claude Design mockup (02b-operations-burst): a single gold "⚡ הפעל BURST" button on the legal-halacha-drain row that opens a deadline dialog (datetime-local, default the upcoming Saturday 18:00, quick chips for Sat 18:00 / +5h / midnight). While a burst is active the row shows a gold pill with the deadline + an "עצור BURST" stop button. Manual only. - operations.ts: burst_until on OpsService + useDrainBurst hook (POST .../burst). - page.tsx: BurstControl component, gated to legal-halacha-drain. - types.ts: regenerated (npm run api:types) — the new burst route. Active-state is derived from burst_until presence (the supervisor NULLs it at the deadline, snapshot refetches every 5s) — no impure Date.now() in render. Invariants: G1/G2 — single DB-backed control (drain_controls.burst_until), shared with the host supervisor; no parallel path. UI passed the Claude Design gate. --- web-ui/src/app/operations/page.tsx | 137 +++++++++++++++++++++++++++++ web-ui/src/lib/api/operations.ts | 23 +++++ web-ui/src/lib/api/types.ts | 63 +++++++++++++ 3 files changed, 223 insertions(+) diff --git a/web-ui/src/app/operations/page.tsx b/web-ui/src/app/operations/page.tsx index 8a69e17..f3e3725 100644 --- a/web-ui/src/app/operations/page.tsx +++ b/web-ui/src/app/operations/page.tsx @@ -8,6 +8,8 @@ import { Card, CardContent } from "@/components/ui/card"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; import { Switch } from "@/components/ui/switch"; +import { Input } from "@/components/ui/input"; +import { Label } from "@/components/ui/label"; import { Skeleton } from "@/components/ui/skeleton"; import { ScrollArea } from "@/components/ui/scroll-area"; import { @@ -16,11 +18,13 @@ import { DialogHeader, DialogTitle, DialogDescription, + DialogFooter, } from "@/components/ui/dialog"; import { useOperations, useServiceAction, useDrainToggle, + useDrainBurst, useAgentRuns, useRunLog, useCancelRun, @@ -116,6 +120,138 @@ const SERVICE_LABELS: Record = { "legal-chat-service": "שירות צ׳אט אימון (גשר ל-claude CLI)", }; +// ── BURST control (halacha drain) — manual "run continuously now until X" ── +function pad2(n: number): string { + return String(n).padStart(2, "0"); +} +/** Format a Date as a datetime-local input value ("YYYY-MM-DDTHH:MM"), local tz. */ +function toLocalInput(d: Date): string { + return `${d.getFullYear()}-${pad2(d.getMonth() + 1)}-${pad2(d.getDate())}T${pad2(d.getHours())}:${pad2(d.getMinutes())}`; +} +/** The upcoming Saturday at 18:00, local time. */ +function nextSaturday18(): Date { + const d = new Date(); + d.setHours(18, 0, 0, 0); + d.setDate(d.getDate() + ((6 - d.getDay() + 7) % 7)); // Sat = getDay() 6 + if (d.getTime() <= Date.now()) d.setDate(d.getDate() + 7); + return d; +} +const HE_DAYS = ["א׳", "ב׳", "ג׳", "ד׳", "ה׳", "ו׳", "ש׳"]; +function fmtDeadline(iso: string): string { + const d = new Date(iso); + return `${HE_DAYS[d.getDay()]} ${pad2(d.getDate())}/${pad2(d.getMonth() + 1)} ${pad2(d.getHours())}:${pad2(d.getMinutes())}`; +} + +function BurstControl({ s }: { s: OpsService }) { + const burst = useDrainBurst(); + const [open, setOpen] = useState(false); + const [until, setUntil] = useState(() => toLocalInput(nextSaturday18())); + // The backend (supervisor) is the authority on expiry — it NULLs burst_until at + // the deadline (snapshot refetches every 5s), so presence ⇒ active. Avoids an + // impure Date.now() during render. + const active = !!s.burst_until; + + if (active) { + return ( + + + ⚡ BURST · עד {fmtDeadline(s.burst_until!)} + + + + ); + } + + return ( + <> + + + + + ⚡ הפעלת BURST — חילוץ הלכות רצוף + + הדריינר ירוץ ברצף מעכשיו (מתעלם מחלון-הלילה) ויעבד את תור-ההלכות עד המועד שתבחר, + תוך ניצול מכסת-Claude הפנויה. נעצר אוטומטית במועד — או ידנית בכפתור "עצור BURST". + + +
+ + setUntil(e.target.value)} + /> +
+ + + +
+

+ ℹ️ הפעלה ידנית בלבד — המערכת לא תפעיל BURST לבד. נכנס לתוקף תוך ≤15 דק׳ (מחזור המתזמר). +

+
+ + + + +
+
+ + ); +} +function busyBurst(pending: boolean, disabled?: boolean): boolean { + return pending || !!disabled; +} + // ── Process-management panel (the "Windows services" view) ───────────────── function ServiceControls({ s, @@ -143,6 +279,7 @@ function ServiceControls({ > הרץ עכשיו + {s.name === "legal-halacha-drain" && }