feat(workflow-bar): processing-only elapsed timer + optimistic phase paint on resume
Timer now accumulates only while the harness works, pausing while a gate awaits the reviewer and freezing when finalized (keyed per session, in-memory). Resume now colours the re-entry phase immediately from the manifest's phase (1..8), mirroring the F1 paint for new questions. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -15,12 +15,12 @@ import { Button } from "../components/ui/button";
|
||||
import { Toaster } from "../components/ui/sonner";
|
||||
import { toast } from "sonner";
|
||||
import {
|
||||
closeSession, listSessions, resumeSession,
|
||||
closeSession, listSessions, resumeSession, getSession,
|
||||
renameSession, setSessionGroup, archiveSession, unarchiveSession, deleteSession,
|
||||
} from "../api/sessions";
|
||||
import type { SessionSummary } from "../api/types";
|
||||
import { useQuery, useQueryClient } from "@tanstack/react-query";
|
||||
import { useEffect, useMemo, useRef, useState } from "react";
|
||||
import { useMemo, useRef, useState } from "react";
|
||||
|
||||
/**
|
||||
* The page is designed to live INSIDE the Omics Portal chrome (its left sidebar
|
||||
@@ -55,12 +55,6 @@ export function AppShell() {
|
||||
const refresh = () => queryClient.invalidateQueries({ queryKey: ["sessions"] });
|
||||
const activeSession = sessions.find((s) => s.id === activeSessionId) ?? null;
|
||||
const finalized = activeSession?.status === "finalized";
|
||||
const [localStart, setLocalStart] = useState<string | null>(null);
|
||||
useEffect(() => {
|
||||
if (activeSessionId && !activeSession) setLocalStart((prev) => prev ?? new Date().toISOString());
|
||||
else if (!activeSessionId) setLocalStart(null);
|
||||
}, [activeSessionId, activeSession]);
|
||||
const startedAt = activeSession?.created_at ?? localStart;
|
||||
|
||||
function openPanel(id: string) {
|
||||
const s = sessions.find((x) => x.id === id);
|
||||
@@ -78,6 +72,17 @@ export function AppShell() {
|
||||
await resumeSession(id);
|
||||
setPanelSession(null);
|
||||
setActiveSessionId(id);
|
||||
// Optimistic phase paint on resume: colour the re-entry phase immediately,
|
||||
// before the first gate arrives (mirrors the F1 paint for new questions).
|
||||
// The manifest's `phase` is the 1-based current phase (1..8).
|
||||
try {
|
||||
const m = (await getSession(id)) as { phase?: number };
|
||||
if (typeof m.phase === "number" && m.phase >= 1 && m.phase <= 8) {
|
||||
setPhase(`F${m.phase}`);
|
||||
}
|
||||
} catch {
|
||||
/* non-fatal: the first gate will set the phase */
|
||||
}
|
||||
} catch {
|
||||
toast.error("Failed to resume session.");
|
||||
}
|
||||
@@ -141,7 +146,11 @@ export function AppShell() {
|
||||
// waiting on the human; once a widget appears, input is back in the user's court.
|
||||
const pendingWidget = useSessionStore((s) => s.pendingWidget);
|
||||
const resetSession = useSessionStore((s) => s.resetSession);
|
||||
const setPhase = useSessionStore((s) => s.setPhase);
|
||||
const working = Boolean(activeSessionId) && !pendingWidget;
|
||||
// Processing time counts only while the harness works, not while a finalized
|
||||
// session sits idle or a gate awaits the reviewer (pendingWidget).
|
||||
const running = working && !finalized;
|
||||
|
||||
useSessionStream(activeSessionId);
|
||||
|
||||
@@ -179,8 +188,8 @@ export function AppShell() {
|
||||
<div className="mx-auto max-w-3xl">
|
||||
<WorkflowBar
|
||||
finalized={finalized}
|
||||
createdAt={startedAt}
|
||||
updatedAt={activeSession?.updated_at ?? null}
|
||||
running={running}
|
||||
resetKey={activeSessionId}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user