fix(word-addin): abort workflow runs on unmount and add a visible Stop control
WHY THIS MATTERS
WorkflowPicker's handleRun started a server-sent-events (SSE) chat
stream with no AbortSignal and no mounted guard. Switching tabs mid-run
therefore (1) left the HTTP stream open, so the backend kept the LLM
call running and kept billing the user's tokens for an answer nobody
would ever see, and (2) let every later chunk call setState on an
unmounted component - wasted work and a classic React leak pattern.
ChatPanel and DocumentActions already implement the correct discipline;
this brings the remaining two panels in line, and gives the user a
Stop button so a run started by mistake can be cancelled at all.
WHAT IS THE ABORT/MOUNTED DISCIPLINE
Two refs plus one cleanup effect:
const abortRef = useRef<AbortController | null>(null);
const mountedRef = useRef(true);
useEffect(() => {
mountedRef.current = true;
return () => { // runs when the tab unmounts the panel
mountedRef.current = false;
abortRef.current?.abort(); // tears down the fetch + SSE reader
};
}, []);
`AbortController.signal` is threaded into fetch; aborting it closes the
connection, which is the backend's cue to cancel the upstream LLM call.
`mountedRef` guards every setState that can fire after an await, because
a promise resolving is not evidence the component still exists. In the
catch block, `controller.signal.aborted` distinguishes a deliberate
stop (keep the partial text, show no error) from a real failure.
HOW THE FIX WORKS
- WorkflowPicker: handleRun now creates an AbortController per run,
passes its signal to streamAssistant, guards all setState with
mountedRef, and treats an aborted run as "keep the partial result".
While a run is streaming, the Run button is swapped for a Stop
button (same pattern as ChatPanel's composer Stop), wired to
abortRef - the visible cancel the panel never had. The initial
listWorkflows() load gets the same mounted guard.
- ProjectPicker: the upload flow gets a mountedRef guard so an upload
resolving after a tab switch no longer setStates on an unmounted
component. The upload request itself cannot be aborted mid-flight -
the shared uploadProjectDocument() accepts no AbortSignal (and the
vendored client is deliberately untouched) - but unlike an LLM
stream an upload is cheap, so discarding the response suffices.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
| Repository | open-legal-products/mike |
|---|---|
| Author | Amal <mamalanand3@gmail.com> |
| Authored | |
| Committed | |
| Parents | 67a8d54e |
| Stats | 2 files changed , +76 , -17 |
| Part of | Add a Word add-in for chat and tracked rewrites |
Capture this commit into my fork
Download a Markdown prompt that tells Claude how to port this
exact commit into your working tree. Run it via
claude -p < capture-commit-b87520fe.md
from inside the repo you want the change in.