Comfy-Org / Comfy-Org/Comfy-Desktop

Unify in-flight operation tracking so all windows reflect every operation (dashboard hydration)

Open
#900 1 comment 0 reactions 0 assignees View on GitHub
Dominant language
TypeScript
Stars
458
Forks
59
Avg merge
22h 18m
Merged PRs (30d)
45

Description

## Summary

Newly-opened windows (and the dashboard generally) do not see in-flight **operations** (update / migrate / delete / copy / restore) or several lifecycle states started elsewhere. We should unify operation tracking so any window reflects every in-flight op, reusing the registry the title-bar picker popup already relies on rather than building a parallel system.

Spun out of the dashboard status-pill work (PR #874), where we added hydration for `runningInstances` and `launchingInstances` but deliberately stopped at the bigger operation-tracking change.

## Background — two execution models

Operations run through **two different paths** today:

- **Renderer-owned ops (dashboard / panel):** the initiating renderer's `progressStore.showProgress` drives `window.api.runAction(...)`. Main reports progress **point-to-point** via `sender.send('install-progress', ...)` to that one window, and registers only the abort handle in `_operationAborts`. No rich status is recorded globally.
- **Main-owned ops (title-bar picker popup):** the popup is ephemeral, so main runs the op via `pickerRunBackgroundOp(...)` and tracks full status in `_activeOperationStatus` (`Map`), pushing it to the popup via the picker snapshot.

`_activeOperationStatus` is therefore **only populated for picker-initiated ops** (`_activeOperationStatus.set` lives exclusively inside `pickerRunBackgroundOp` in `src/main/index.ts`). If the dashboard naively consumed it, it would show picker ops and miss every dashboard/panel-initiated op — a misleading half-view. That's why the dashboard doesn't currently read it.

## Current hydration matrix

| State | Live to all windows | Hydrated on a new window's `init()` |
|---|---|---|
| `runningInstances` | yes (broadcast) | yes (`get-running-instances`) |
| `launchingInstances` | yes (broadcast) | yes (`get-launching-instances`, added in #874) |
| `stoppingInstances` | yes (broadcast) | no (main keeps no queryable stopping set) |
| error — crash | yes (broadcast) | per-install pull (`getLastCrashError`) in the instance window only; dashboard not bulk-hydrated |
| error — op failure | no (initiator only) | no |
| operations / progress bars | no (`sender.send` initiator only) | no |

## Proposed work

**Small, low-risk gaps (same pattern as the launching fix):**
1. Retain a stopping set in main + a `get-stopping-instances` snapshot so a window opened mid-stop shows "Stopping…".
2. Add a bulk `get-error-instances` snapshot so a fresh dashboard shows red tiles for crashes/op-failures that occurred before it opened.

**Operation-registry unification (the real fix):**
3. Make the renderer-owned `runAction` flow also write into `_activeOperationStatus` (hook via `makeSendProgress`), so the registry is the single source of truth for all ops.
4. Broadcast `install-progress` to all windows (or emit a snapshot-changed signal like the picker loop) so progress is shared.
5. Add `get-active-operations` so a newly-opened window hydrates in-flight ops into its `progressStore` (mirrors `get-launching-instances`).

## Risks / open questions

- **Cancel & ownership semantics.** A renderer-owned op is today implicitly tied to its initiating window's `ProgressModal`. Once shared, we must define: what does "cancel from another window" do, and what happens to the op (and its modal) when the initiating window closes? `_operationAborts` is already global/main-side, so the op itself survives window close — only the progress UI is window-bound today.
- Avoid double-counting between the existing picker path and the new shared path.

## References
- `src/main/lib/ipc/shared.ts` — `_activeOperationStatus`, `_runningSessions`, `_getLaunchingInstances`
- `src/main/index.ts` — `pickerRunBackgroundOp` (only writer of `_activeOperationStatus`)
- `src/main/lib/ipc/broadcast.ts` — `_broadcastToRenderer` (all windows) vs `sender.send` (initiator only)
- `src/renderer/src/stores/progressStore.ts` — renderer-owned op lifecycle
- `src/renderer/src/stores/sessionStore.ts` — `init()` hydration

Contributor guide

No contributing guide indexed for this repository

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.