Files
orca/src/main/runtime/rpc/methods/client-ui.ts
T
Jinwoo-H c46fbcf9c4 fix(repos): stop the host echoing pairing-local UI state back to clients (STA-4850)
Stripping manualRepoOrder on the write path left the read path open. ui.get
still returned the host's array, and the web client's merge let it win. On a
profile an old web client had already poisoned, the runtime:web-<uuid> entries
echoed back to the very browser that minted them — the environment id is
persisted per pairing — so they matched its restamped repos and overrode every
drag it made after. Even on a clean profile the desktop-keyed array (or [])
replaced the browser's own order on each reload.

Close both ends: the host omits these fields from every ui read response, and
the web client pins its local values when merging one. Either half alone fixes
one skew direction; both are needed because clients and hosts update apart.

Replace the hand-maintained destructures with PAIRING_LOCAL_UI_FIELDS, one
exported set consumed at all four seams, so a field added to it cannot be wired
into some seams and forgotten in others. A census test pins the membership and
the seam tests iterate it. hideWorkspacesFromOtherDevices joins the set with no
behavior change: the web client already pinned it locally, and mobile's only
ui.get consumer reads trustedOrcaHooks.

Also pin the preserve branch in the store: an update that omits manualRepoOrder
must keep the stored order, since after the strip that is every paired ui.set.
2026-08-19 19:09:52 -04:00

75 lines
2.6 KiB
TypeScript

import { omitPairingLocalUiFields } from '../../../../shared/pairing-local-ui-fields'
import type { PersistedUIState } from '../../../../shared/persisted-ui-state-types'
import { defineMethod, type RpcMethod } from '../core'
import {
FeatureInteractionIdParam,
PRBotAuthorOverrideUpdate,
SettingsUpdate,
UiUpdate
} from './client-ui-schemas'
// Type-only side effect: keeps the schema/PersistedUIState parity assertions in
// the typecheck graph so drift fails the build instead of a paired client.
import { TerminalQuickCommandsUpdate } from './terminal-quick-command-rpc-schema'
export const CLIENT_UI_METHODS: RpcMethod[] = [
defineMethod({
name: 'settings.get',
params: null,
handler: (_params, { runtime }) => ({ settings: runtime.getClientSettings() })
}),
defineMethod({
name: 'settings.update',
params: SettingsUpdate,
handler: async (params, { runtime }) => ({
settings: await runtime.updateClientSettings(params)
})
}),
defineMethod({
name: 'settings.getTerminalQuickCommands',
params: null,
// Why: command bodies can total ~240 KB, so keep unrelated settings reads
// from carrying them over every paired/relay connection.
handler: (_params, { runtime }) => ({
terminalQuickCommands: runtime.getClientTerminalQuickCommands()
})
}),
defineMethod({
name: 'settings.updateTerminalQuickCommands',
params: TerminalQuickCommandsUpdate,
handler: (params, { runtime }) => ({
terminalQuickCommands: runtime.updateClientTerminalQuickCommands(params.mutation)
})
}),
defineMethod({
name: 'settings.updatePRBotAuthorOverride',
params: PRBotAuthorOverrideUpdate,
handler: (params, { runtime }) => ({
settings: runtime.updateClientPRBotAuthorOverride(params)
})
}),
defineMethod({
name: 'ui.get',
params: null,
handler: (_params, { runtime }) => ({ ui: omitPairingLocalUiFields(runtime.getUIState()) })
}),
defineMethod({
name: 'ui.set',
params: UiUpdate,
// Why the fields are dropped here rather than removed from the schema: UiUpdate is strict, so
// an unlisted key would make the dispatcher reject an old client's ENTIRE payload.
handler: (params, { runtime }) => ({
ui: omitPairingLocalUiFields(
runtime.updateUIState(omitPairingLocalUiFields(params) as Partial<PersistedUIState>)
)
})
}),
defineMethod({
name: 'ui.recordFeatureInteraction',
params: FeatureInteractionIdParam,
handler: (params, { runtime }) => ({
ui: omitPairingLocalUiFields(runtime.recordFeatureInteraction(params))
})
})
]