fix(mobile): skip floating workspace repo lookup

This commit is contained in:
Brennan Benson
2026-07-20 17:45:36 -07:00
parent 0a454cc801
commit c308df1add
2 changed files with 29 additions and 7 deletions
@@ -2,6 +2,7 @@ import { createElement } from 'react'
import { act, create, type ReactTestRenderer } from 'react-test-renderer'
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import type { RpcClient } from '../transport/rpc-client'
import { FLOATING_WORKSPACE_WORKTREE_ID } from './floating-workspace'
import { useMobileNativeChatReadability } from './use-mobile-native-chat-readability'
describe('useMobileNativeChatReadability', () => {
@@ -18,15 +19,19 @@ describe('useMobileNativeChatReadability', () => {
renderer = null
})
async function mount(connectionId: string | null): Promise<void> {
async function mount(
connectionId: string | null,
worktreeId = 'repo::/worktree'
): Promise<ReturnType<typeof vi.fn>> {
const sendRequest = vi.fn().mockResolvedValue({
ok: true,
result: { repos: [{ id: 'repo', connectionId }] }
})
const client = {
sendRequest: vi.fn().mockResolvedValue({
ok: true,
result: { repos: [{ id: 'repo', connectionId }] }
})
sendRequest
} as unknown as RpcClient
function Harness(): null {
readable = useMobileNativeChatReadability(client, 'repo::/worktree')
readable = useMobileNativeChatReadability(client, worktreeId)
return null
}
const original = console.error
@@ -44,6 +49,7 @@ describe('useMobileNativeChatReadability', () => {
} finally {
consoleSpy.mockRestore()
}
return sendRequest
}
it('admits local and runtime-owned transcript hosts', async () => {
@@ -61,6 +67,13 @@ describe('useMobileNativeChatReadability', () => {
expect(readable).toBe(false)
})
it('treats the host-local floating workspace as readable without listing repos', async () => {
const sendRequest = await mount(null, FLOATING_WORKSPACE_WORKTREE_ID)
expect(readable).toBe(true)
expect(sendRequest).not.toHaveBeenCalled()
})
it('fails closed immediately while a reused route resolves its new worktree', async () => {
let resolveNext: (response: unknown) => void = () => {}
const client = {
@@ -1,5 +1,6 @@
import { useEffect, useState } from 'react'
import type { RpcClient } from '../transport/rpc-client'
import { isFloatingWorkspaceWorktreeId } from './floating-workspace'
import { isMobileNativeChatTranscriptReadable } from './mobile-native-chat-eligibility'
import { getRepoIdFromMobileWorktreeId } from './mobile-session-route-helpers'
@@ -10,12 +11,17 @@ export function useMobileNativeChatReadability(
client: RpcClient | null,
worktreeId: string
): boolean {
const isFloatingWorkspace = isFloatingWorkspaceWorktreeId(worktreeId)
const [state, setState] = useState<ReadabilityState>({
client: null,
worktreeId: '',
readable: false
})
useEffect(() => {
// Why: the floating workspace always runs on the paired host and has no repo connection to resolve.
if (isFloatingWorkspace) {
return
}
let active = true
if (!client) {
setState({ client, worktreeId, readable: false })
@@ -46,7 +52,10 @@ export function useMobileNativeChatReadability(
return () => {
active = false
}
}, [client, worktreeId])
}, [client, isFloatingWorkspace, worktreeId])
if (isFloatingWorkspace) {
return true
}
// Why: route reuse renders before its new effect resolves; never expose the
// previous repo's readability under a different client/worktree key.
return state.client === client && state.worktreeId === worktreeId ? state.readable : false