fix(tui): suppress Working loader for blocking custom UI overlays - #1677
fix(tui): suppress Working loader for blocking custom UI overlays#1677flora131 wants to merge 11 commits into
Conversation
Hide the global Working row while workflow slash-command overlays are awaiting user input, then restore visibility when they settle or hand off to real work. Covers inline workflow inputs, session picker/connect, resume selector, and related overlay confirm paths. Refs: #1670 Assistant-model: GPT-5.5
Review:
|
Assistant-model: GPT-5.5
Review: fix(workflows): hide Working spinner during workflow overlaysNice, focused fix for #1670. The extraction of What is solid
Minor nits (non-blocking)
One thing to confirm
Overall this is a clean, well-tested, correctly-scoped fix. LGTM pending green CI. |
Assistant-model: GPT-5.5
Code Review —
|
|
Implementation direction I want to switch toward: Instead of having each workflow overlay manually hide/restore the global The core issue is a UI-state mismatch: after the eager spinner change, Preferred approach:
This keeps ownership in the right layer: overlays should not need to know about global spinner state. The current helper-based solution is safe and pragmatic, but it spreads global UI-state responsibility into individual overlay implementations. Central suppression should be cleaner and less error-prone for future overlays. |
…overlays # Conflicts: # packages/workflows/CHANGELOG.md
Review:
|
Review: fix(workflows): hide Working spinner during workflow overlaysThanks for this — it's a well-scoped, thoroughly-tested fix for #1670. The extraction of Strengths
Minor points (non-blocking)
TestsCoverage is strong across all lifecycle paths and both failure modes. I couldn't execute Overall this is solid and ready to merge once the picker→confirm toggle (point 1) is confirmed harmless. 👍 |
…overlays # Conflicts: # packages/coding-agent/CHANGELOG.md
Review: fix(workflows): hide Working spinner during workflow overlaysNice, well-scoped fix. Moving spinner suppression into the host (auto-suppress while a non-overlay A few things worth addressing before merge. 🔴 Stale capability gate — inline form now depends on
|
Assistant-model: GPT-5.5
Code Review —
|
Summary
Fixes #1670: the host's global
Working...spinner kept rendering underneath workflow slash-command overlays that were waiting on user input (e.g./workflow <name>input forms, the session connect/resume pickers, the kill-confirm dialog), making the TUI look busy when it was actually idle and blocked on a keypress.Rather than having each overlay manually hide/restore the spinner, the fix centralizes suppression in the interactive host: any non-overlay
ctx.ui.custom()mount (the default, or explicit{ overlay: false }) is now treated as blocking user input and automatically suppressesWorking...for as long as it's mounted, restoring it when the component settles or is dismissed. Floating{ overlay: true }custom UIs are left alone since they can be passive views over still-active work (e.g. the kill-confirm modal). The workflows package's overlays are updated to rely on this host behavior instead of duplicating hide/restore logic, andinline-form-overlay.tsis migrated from the legacysetEditorComponent/getEditorComponenteditor-swap to mounting throughctx.ui.custom()directly so it participates in the same lifecycle.Changes
interactive-extension-runtime.ts/interactive-extension-context.ts/interactive-agent-events.ts/interactive-mode-surface.ts: AddedisWorkingLoaderAllowed()(true only whenworkingVisibleandblockingInlineCustomUiDepth === 0) andrefreshWorkingLoaderVisibility(), and wiredbeginHostInlineCustomUi/setWorkingVisible/the agent-event loader restart path through them so the spinner is suppressed for the full lifetime of any blocking inline custom UI, not just whileworkingVisibleis explicitly toggled off.inline-form-overlay.ts:openInlineInputsFormnow mounts the inline form viactx.ui.custom(factory, { overlay: false })instead of swapping in a custom editor withsetEditorComponent/getEditorComponent, removing the bespoke "was our factory still installed" staleness checks needed by the old swap-and-restore approach (including the/new//resume//fork//reloadstale-context case). Working-loader suppression is now handled entirely by the host for the duration of the mount.inputs-overlay.ts: RemovedopenInputsPicker's ownhideWorking/restoreWorkingclosures and thesetWorkingVisiblesurface requirement — the host now suppresses the spinner for the picker's mounted lifetime.session-overlays.ts: Removed manual Working-visibility handling fromopenSessionPicker/openKillConfirm. Hardened mount-failure handling so both a synchronous throw and an async rejection fromcustom(...)resolve to a safe default ({ kind: "close" }/false) instead of leaving the promise unsettled, and addedConfirmUiSurfaceto the surface types.workflow-resume-selector.ts: Applied the same defensive synchronous/async mount-failure handling toopenWorkflowResumeSelector, settling to{ kind: "close" }on either failure path.workflow-command-registration.ts: AlignedcanOpenPicker's capability gate to only requirectx.ui.custom(dropped the now-deadtypeof ctx.ui?.setEditorComponent === "function"fallback check), and always route throughopenInlineInputsFormrather than gating that call onsetEditorComponentsupport, so custom-only UI hosts correctly reach the inline input form instead of skipping straight to the picker fallback.inline-form-editor.ts: Updated doc comments to describe the component as mounted via non-overlayctx.ui.custom()rather than swapped in viasetEditorComponent.packages/coding-agent/docs/extensions.mdanddocs/tui.mddocument that non-overlayctx.ui.custom()suppresses the globalWorking...loader while mounted, and that{ overlay: true }opts out.[Unreleased] / Fixedin bothpackages/coding-agent/CHANGELOG.mdandpackages/workflows/CHANGELOG.md.Tests
packages/coding-agent/test/interactive-mode-status-custom-ui.suite.ts: new coverage asserting the Working loader is stopped for the duration of a non-overlayshowExtensionCustommount and restarted after it settles, and that overlay ({ overlay: true }) mounts do not suppress it.test/unit/overlay-mount-placement.test.ts: new assertions thatopenSessionPicker/openKillConfirm/openWorkflowResumeSelectornever callsetWorkingVisiblethemselves (connect, close, dispose, and mount-failure paths), confirming visibility is left entirely to the host.test/unit/inline-form-04.test.ts,test/unit/inline-form-helpers.ts,test/unit/inputs-picker-01.ts,test/unit/inputs-picker-helpers.ts: updated to mountopenInlineInputsForm/openInputsPickerthrough thectx.ui.custom()surface instead of the retiredsetEditorComponent/getEditorComponentmocks.test/integration/mock-extension-api-workflow-actions.test.ts/test/integration/overlay-entrypoints-helpers.ts: new coverage confirming custom-only UI hosts (nosetEditorComponent) reach the inline workflow input form instead of skipping to the fallback picker.Validation
AGENT=1 bun test test/unit/inline-form-04.test.ts test/unit/inputs-picker-01.ts test/unit/overlay-mount-placement.test.ts packages/coding-agent/test/interactive-mode-status-custom-ui.suite.tsAGENT=1 bun run typecheckAGENT=1 bun run check:file-lengthAGENT=1 bun run test:unit