From 5ec0b2698fbb74574650d21f96ccef8d4cb33d4f Mon Sep 17 00:00:00 2001 From: Jinwoo-H Date: Mon, 14 Sep 2026 02:27:32 -0400 Subject: [PATCH] test(mobile): pin the ui.get trust blank, and correct two stale acceptance comments MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The goldens cannot catch a regression to `if (uiResult?.result)`: the scenario's success reply is `{"ui":{}}`, so every partition of matrix-settings.workspace-context-ui.get-1 records the same `trust:{}` state. The new case answers once with real trust and again with a null result on a fresh client, which is the only shape where skipping the blank is observable — trustedOrcaHooks gates the setup-hook approval prompt in use-new-workspace-create-submit.ts, so a stale value would skip it. settingsRead's comment still claimed workspace context, which this branch moved to optionalSettingsRead; both comments now name their real callers. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb --- .../use-new-workspace-runtime-context.test.ts | 41 +++++++++++++------ .../src/transport/settings-read-operations.ts | 4 +- 2 files changed, 30 insertions(+), 15 deletions(-) diff --git a/mobile/src/components/use-new-workspace-runtime-context.test.ts b/mobile/src/components/use-new-workspace-runtime-context.test.ts index cc98330b4ed..f189d52e3b0 100644 --- a/mobile/src/components/use-new-workspace-runtime-context.test.ts +++ b/mobile/src/components/use-new-workspace-runtime-context.test.ts @@ -40,25 +40,31 @@ function clientAnswering(settingsResult: unknown, uiResult: unknown): RpcClient describe('useNewWorkspaceRuntimeContext', () => { let renderer: ReactTestRenderer | null = null + let context: RuntimeContext | null = null afterEach(() => { act(() => renderer?.unmount()) renderer = null + context = null }) - async function mount(settingsResult: unknown, uiResult: unknown): Promise { - // One client for the whole mount: the hook keys its effect on client identity. - const client = clientAnswering(settingsResult, uiResult) - let context!: RuntimeContext - function Harness(): null { - context = useNewWorkspaceRuntimeContext(client, true) - return null - } + function Harness({ client }: { client: RpcClient }): null { + context = useNewWorkspaceRuntimeContext(client, true) + return null + } + + /** Answering twice re-renders the live harness, so the second call is a host swap, not a remount. */ + async function answer(settingsResult: unknown, uiResult: unknown): Promise { + const element = createElement(Harness, { client: clientAnswering(settingsResult, uiResult) }) await act(async () => { - renderer = create(createElement(Harness)) + if (renderer) { + renderer.update(element) + } else { + renderer = create(element) + } }) await act(async () => {}) - const { runtimeSettings, trustedOrcaHooks, availableProviders } = context + const { runtimeSettings, trustedOrcaHooks, availableProviders } = context! return { runtimeSettings, trustedOrcaHooks, availableProviders } } @@ -69,7 +75,7 @@ describe('useNewWorkspaceRuntimeContext', () => { ['absent', undefined], ['without a settings member', {}] ])('degrades a %s settings result to absent settings', async (_label, settingsResult) => { - expect(await mount(settingsResult, UI_WITH_TRUST)).toEqual({ + expect(await answer(settingsResult, UI_WITH_TRUST)).toEqual({ runtimeSettings: null, trustedOrcaHooks: TRUSTED_HOOKS, availableProviders: ['github'] @@ -83,15 +89,24 @@ describe('useNewWorkspaceRuntimeContext', () => { ['absent', undefined], ['without a ui member', {}] ])('degrades a %s ui result to untrusted hooks', async (_label, uiResult) => { - expect(await mount({ settings: SETTINGS }, uiResult)).toEqual({ + expect(await answer({ settings: SETTINGS }, uiResult)).toEqual({ runtimeSettings: SETTINGS, trustedOrcaHooks: {}, availableProviders: ['github'] }) }) + // The blank, not just the absence of a throw: trustedOrcaHooks gates the setup-hook approval + // prompt in use-new-workspace-create-submit.ts, so a stale value would skip it. + it('blanks the trust an earlier host published when the next ui result is null', async () => { + expect((await answer({ settings: SETTINGS }, UI_WITH_TRUST)).trustedOrcaHooks).toEqual( + TRUSTED_HOOKS + ) + expect((await answer({ settings: SETTINGS }, null)).trustedOrcaHooks).toEqual({}) + }) + it('publishes the settings and trust a host does send', async () => { - expect(await mount({ settings: SETTINGS }, UI_WITH_TRUST)).toEqual({ + expect(await answer({ settings: SETTINGS }, UI_WITH_TRUST)).toEqual({ runtimeSettings: SETTINGS, trustedOrcaHooks: TRUSTED_HOOKS, availableProviders: ['github'] diff --git a/mobile/src/transport/settings-read-operations.ts b/mobile/src/transport/settings-read-operations.ts index 8a17f0a6c2f..237e5711d84 100644 --- a/mobile/src/transport/settings-read-operations.ts +++ b/mobile/src/transport/settings-read-operations.ts @@ -38,7 +38,7 @@ const botOverridesReader: RpcCompatibleReader = } } -/** Workspace context, submit, task hydration/create and home providers share this acceptance. */ +/** Submit, task hydration/create and home providers: a null result throws the settings read. */ export const settingsRead = bindDeferredRpcOperation( defineRpcOperation({ name: 'settings.member-or-skip', @@ -49,7 +49,7 @@ export const settingsRead = bindDeferredRpcOperation( }) ) -/** A null or absent result reads as absent settings instead of throwing the property read. */ +/** Workspace context, history resume and repo metadata: a null result reads as absent settings. */ export const optionalSettingsRead = bindDeferredRpcOperation( defineRpcOperation({ name: 'settings.optional-member-or-skip',