diff --git a/frontend/packages/agent/src/Root.tsx b/frontend/packages/agent/src/Root.tsx index 8d5fb605..7aabfc37 100644 --- a/frontend/packages/agent/src/Root.tsx +++ b/frontend/packages/agent/src/Root.tsx @@ -155,7 +155,7 @@ export function Root() { paused={false} onTogglePause={() => {}} thinking={false} - onCancelTurn={() => {}} + onCancelTurn={() => Promise.resolve()} />
@@ -210,12 +210,12 @@ export function Root() { ); }} thinking={effectiveTurnState === 'thinking'} - onCancelTurn={() => { + onCancelTurn={() => postCancelTurn().then((r) => { if (!r.ok) liveStreamRef.current?.pushNote(`✗ /cancel failed${r.detail ? ': ' + r.detail : ''}`); refresh(); - }); - }} + }) + } />
diff --git a/frontend/packages/agent/src/components/StatusChips.tsx b/frontend/packages/agent/src/components/StatusChips.tsx index 52cf5bcb..8c00f94e 100644 --- a/frontend/packages/agent/src/components/StatusChips.tsx +++ b/frontend/packages/agent/src/components/StatusChips.tsx @@ -9,7 +9,7 @@ // selection state live in the caller (`Root.tsx`, via the // `useAgentState` hook), so this component can still be demoed and // reviewed against plain sample data independent of live `/api/state`. -import { useEffect, useState } from 'preact/hooks'; +import { useState } from 'preact/hooks'; import { Badge, type BadgeTone } from '@hive/shared/badge.js'; import { Dropdown, type DropdownOption } from '@hive/shared/dropdown.js'; import './StatusChips.css'; @@ -32,7 +32,7 @@ export interface StatusChipsProps { onTogglePause: () => void; lastTurnLabel?: string; thinking: boolean; - onCancelTurn: () => void; + onCancelTurn: () => Promise; } const MODEL_DESCRIPTIONS: Record = { @@ -105,14 +105,14 @@ export function StatusChips({ thinking, onCancelTurn, }: StatusChipsProps) { + // `.finally()` on the caller's promise (not a `thinking`-flip effect): + // re-enables the button once the request actually settles, success or + // failure alike — ported from app.js's original + // `postCancelTurn().finally(() => { btn.disabled = false; })`. A + // `thinking`-only reset would leave the button stuck disabled on a + // failed request (argus, PR review): the turn is still in flight, so + // `thinking` never flips to trigger a reset. const [cancelBusy, setCancelBusy] = useState(false); - // The button unmounts (not just hides) once `thinking` flips false, but - // `cancelBusy` lives in this component's own state across re-renders — - // reset it here so a *later* turn doesn't inherit a stale "busy" from a - // previous cancel click. - useEffect(() => { - if (!thinking) setCancelBusy(false); - }, [thinking]); return (
@@ -160,7 +160,7 @@ export function StatusChips({ disabled={cancelBusy} onClick={() => { setCancelBusy(true); - onCancelTurn(); + onCancelTurn().finally(() => setCancelBusy(false)); }} > ■ cancel turn