Skip to content

Add API spec for find() sibling wait and RECONNECT structure event#23

Open
stefanrammo wants to merge 1 commit intomainfrom
spec/add-find-wait-and-reconnect-event
Open

Add API spec for find() sibling wait and RECONNECT structure event#23
stefanrammo wants to merge 1 commit intomainfrom
spec/add-find-wait-and-reconnect-event

Conversation

@stefanrammo
Copy link
Copy Markdown
Collaborator

Specifies two changes to improve multi-app usability:

  1. find() waits for sibling apps by default instead of failing immediately
  2. subscribeToStructure gets a RECONNECT event (value 2) to distinguish app restarts from first-time appearances

Includes 7 use case examples, backwards compatibility analysis, and comparison with Java/C++ StudioAPI clients.

CDP-6069

Specifies two changes to improve multi-app usability:

1. find() waits for sibling apps by default instead of failing
   immediately
2. subscribeToStructure gets a RECONNECT event (value 2) to
   distinguish app restarts from first-time appearances

Includes 7 use case examples, backwards compatibility analysis,
and comparison with Java/C++ StudioAPI clients.

CDP-6069
@stefanrammo stefanrammo requested review from Karmo7 and nuubik March 18, 2026 11:54
```javascript
client.find('App2.CPULoad') // waits (default timeout) for App2 to appear
client.find('App2.CPULoad', { timeout: 5000 }) // waits up to 5s
client.find('App2.CPULoad', { timeout: 0 }) // fails immediately if App2 not available (old behavior)
Copy link
Copy Markdown

@Karmo7 Karmo7 Mar 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think should allow for no timeout. E.g. if one has a panel that only is meant to show some App2 value then indefinite hang might be fine. Maybe that should be even the default?

- **Immediate fail:** `{ timeout: 0 }` preserves the old behavior — rejects immediately if the app is not available.
- **No prior `root()` required:** `find()` must trigger the connection internally if not already connected. Callers should not need to call `root()` first.
- **Direct mode guard (immediate fail only):** When using `{ timeout: 0 }` in direct mode (no proxy protocol), `find()` rejects with `"AppName is not available"` if the app was previously connected but is now disconnected. This prevents returning stale cached nodes whose underlying WebSocket is down. When waiting (default), this guard does not apply — the wait resolves when the app reconnects. In proxy mode, the primary connection maintains the node tree so the guard is not needed.
- **Direct mode discovery:** In direct mode, `find()` triggers on-demand structure refreshes (every 2s while waiting) to discover new siblings. In proxy mode, discovery is real-time via `ServicesNotification`.
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"every 2s while waiting" - polling should not be necessary, CDP will notify of any new apps appearing. Although I can't remember if one had to subscribe to root node structure or if the notifications come automatically

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think how it worked was that CDP app sends invalidate on root node (that is a structure_change_response with id of 0). And after receiving invalidate of root, CDP should poll for structure change to find sibling apps.

- **Proxy mode:** Sibling app disappears from proxy services, then reappears — fires REMOVE then RECONNECT.
- **Direct mode:** Direct WebSocket connection drops, then reconnects — fires REMOVE then RECONNECT.

**Direct mode discovery:** Without this change, `subscribeToStructure` in direct mode does not discover apps that start after the subscription — it only sees apps that were already connected. This change adds periodic structure polling (every 2s) while subscribers exist, so that ADD/RECONNECT events fire for late-starting apps in direct mode. In proxy mode, discovery is real-time via `ServicesNotification` so no polling is needed. Polling stops automatically when all subscribers unsubscribe or `client.close()` is called.
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Even in direct mode CDP apps notify their siblings are started/stopped. You don't need to poll. One could only consider automatic reconnect for resiliency when first CDP app reports siblings but connection had failed (some networking issue or something).

- **Immediate fail:** `{ timeout: 0 }` preserves the old behavior — rejects immediately if the app is not available.
- **No prior `root()` required:** `find()` must trigger the connection internally if not already connected. Callers should not need to call `root()` first.
- **Direct mode guard (immediate fail only):** When using `{ timeout: 0 }` in direct mode (no proxy protocol), `find()` rejects with `"AppName is not available"` if the app was previously connected but is now disconnected. This prevents returning stale cached nodes whose underlying WebSocket is down. When waiting (default), this guard does not apply — the wait resolves when the app reconnects. In proxy mode, the primary connection maintains the node tree so the guard is not needed.
- **Direct mode discovery:** In direct mode, `find()` triggers on-demand structure refreshes (every 2s while waiting) to discover new siblings. In proxy mode, discovery is real-time via `ServicesNotification`.
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think how it worked was that CDP app sends invalidate on root node (that is a structure_change_response with id of 0). And after receiving invalidate of root, CDP should poll for structure change to find sibling apps.

**Direct mode discovery:** Without this change, `subscribeToStructure` in direct mode does not discover apps that start after the subscription — it only sees apps that were already connected. This change adds periodic structure polling (every 2s) while subscribers exist, so that ADD/RECONNECT events fire for late-starting apps in direct mode. In proxy mode, discovery is real-time via `ServicesNotification` so no polling is needed. Polling stops automatically when all subscribers unsubscribe or `client.close()` is called.

- **Backwards compatible for `=== ADD` checks:** Existing code checking `change === studio.api.structure.ADD` still works — new apps still get ADD. Only restarted apps get RECONNECT.
- **Not compatible with `!== ADD` patterns:** Code that treats any non-ADD change as REMOVE (e.g. `if (change !== ADD) handleRemove()`) will incorrectly trigger on RECONNECT. Such code should be updated to check `change === REMOVE` explicitly.
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we can update the version number to new major version and list the API changes. The RECONNECT sounds like a good change.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants