Skip to content

Let a UI channel declare itself the preferred bridge channel - #246

Open
philipp-winterle wants to merge 1 commit into
httptoolkit:mainfrom
philipp-winterle:feature-ui-channel-priority
Open

philipp-winterle wants to merge 1 commit into
httptoolkit:mainfrom
philipp-winterle:feature-ui-channel-priority

Conversation

@philipp-winterle

@philipp-winterle philipp-winterle commented Sep 17, 2026

Copy link
Copy Markdown

Server half of a two-part change. UI half:
httptoolkit/httptoolkit-ui#271

The problem

src/api/ui-operation-bridge.ts:

private get primaryChannel(): BridgeChannel | undefined {
    return this.channels[0];
}

The primary channel is the one that answers every MCP and CTL operation, whose
operations are the ones exposed, and whose account decides isPaidUser(). It
is whichever UI connected first, for as long as it stays connected.

With the desktop app and a browser tab both pointed at the same server - easy to
end up with, and easy not to notice - which one wins comes down to startup order.
There is no way to steer it and nothing reports which one it is. In my case the
MCP client kept answering out of a stale second window while the app I was
actually driving sat there ignored.

The change

The operations message may now carry an optional priority. The highest one
becomes primary and connection order still breaks ties, so a UI that sends no
priority behaves exactly as it always did. Anything that isn't a finite number is
read as "no preference", so a malformed message can't take the role away from a
well-behaved UI.

Handing the role over mid-session now emits operations-changed for the new
primary, and not-ready if that channel has no operations yet. Previously only a
disconnect could change the primary, so neither case could arise.

The UI counterpart is three lines - the desktop shell sends priority: 1, a
browser tab sends 0:

ws!.send(JSON.stringify({
    type: 'operations',
    operations: registry.getDefinitions(),
    priority: isDesktopShell() ? 1 : 0
}));

Merging this one first is fine: without it the field simply defaults to 0
everywhere and nothing changes.

Test plan

  • Six new tests in test/integration/ui-operation-bridge.spec.ts under
    "Channel priority": a higher-priority channel wins whether it connected
    first or second, execute requests route to it, a non-numeric priority is
    ignored, and the role falls back to a lower-priority channel when the
    preferred one disconnects. Three of the six fail before the change; the
    other three are guards that the existing tie-breaking is untouched.
  • The existing "Multiple channels" tests, which assert the first channel wins
    when nobody declares a priority, still pass unchanged.
  • test:unit: 23 passing.
  • test:integration: 68 passing, 1 pending, 16 failing. main here fails 16
    too, and 62 pass, so the six added tests are the whole difference. The 16 on
    main are all environment-dependent - Docker CLI interception, Electron,
    Chrome, fresh and existing terminal, and the end-to-end server API test -
    none of which this machine can run. I captured that list on main only and
    compared failure counts rather than names, because rerunning the suite here
    launches real browsers and terminal windows.
  • tsc --noEmit clean.

The primary channel - the one that answers every MCP and CTL operation,
and whose account decides isPaidUser() - was whichever UI connected
first. With the desktop app and a browser tab both open, which one that
is comes down to startup order, there's no way to steer it, and nothing
reports which one won.

The operations message now carries an optional priority. The highest one
becomes primary, and connection order still breaks ties, so a UI that
sends no priority behaves exactly as before. Anything that isn't a finite
number is read as no preference, so a malformed message can't take the
role away from a well-behaved UI.

Handing the role over mid-session now emits operations-changed for the
new primary, and not-ready if it has no operations yet - previously only
a disconnect could change primary, so neither case could arise.
@CLAassistant

CLAassistant commented Sep 17, 2026

Copy link
Copy Markdown

CLA assistant check
All committers have signed the CLA.

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