mirror of
https://github.com/stablyai/orca.git
synced 2026-09-22 00:02:31 +00:00
Refresh live worktree display name in mobile session (#5934)
Query the `worktree.show` RPC endpoint on screen focus and poll every 3 seconds to retrieve up-to-date worktree metadata. This ensures that task-generated names and subsequent displayName updates are reflected, rather than relying solely on the initial route parameter entry hint.
This commit is contained in:
@@ -162,6 +162,7 @@ import {
|
||||
} from '../../../../src/session/mobile-clipboard-image'
|
||||
import { useMobileImageAttachment } from '../../../../src/session/use-mobile-image-attachment'
|
||||
import { classifyMobileArtifact } from '../../../../src/session/mobile-artifact-kind'
|
||||
import { useLiveWorktreeName } from '../../../../src/session/use-live-worktree-name'
|
||||
import {
|
||||
buildMarkdownDiskFallbackDoc,
|
||||
shouldReadMarkdownFromDiskAfterReadTabFailure
|
||||
@@ -841,7 +842,7 @@ export default function SessionScreen() {
|
||||
const {
|
||||
hostId,
|
||||
worktreeId,
|
||||
name: worktreeName,
|
||||
name: routeWorktreeName,
|
||||
created,
|
||||
warning: createdWarning
|
||||
} = useLocalSearchParams<{
|
||||
@@ -860,6 +861,12 @@ export default function SessionScreen() {
|
||||
const reconnectAttempts = useReconnectAttempt(hostId)
|
||||
const lastConnectedAt = useLastConnectedAt(hostId)
|
||||
const forceReconnectHost = useForceReconnect()
|
||||
const worktreeName = useLiveWorktreeName({
|
||||
client,
|
||||
connState,
|
||||
routeName: routeWorktreeName,
|
||||
worktreeId
|
||||
})
|
||||
// Master-detail host state (U5/KTD2): on wide layouts a tapped panel docks beside the
|
||||
// session content; on narrow it stays null and the icons push full-screen routes.
|
||||
const { isWideLayout } = useResponsiveLayout()
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
import { useCallback, useEffect, useState } from 'react'
|
||||
import { useFocusEffect } from 'expo-router'
|
||||
import type { RpcClient } from '../transport/rpc-client'
|
||||
import type { ConnectionState, RpcSuccess } from '../transport/types'
|
||||
import { getLiveWorktreeDisplayName, type WorktreeDisplayNameSource } from './worktree-display-name'
|
||||
|
||||
type Params = {
|
||||
client: RpcClient | null
|
||||
connState: ConnectionState
|
||||
routeName?: string
|
||||
worktreeId: string
|
||||
}
|
||||
|
||||
export function useLiveWorktreeName({ client, connState, routeName, worktreeId }: Params): string {
|
||||
const [worktreeName, setWorktreeName] = useState(() => routeName?.trim() ?? '')
|
||||
|
||||
useEffect(() => {
|
||||
setWorktreeName(routeName?.trim() ?? '')
|
||||
}, [routeName, worktreeId])
|
||||
|
||||
useFocusEffect(
|
||||
useCallback(() => {
|
||||
if (!client || connState !== 'connected') {
|
||||
return
|
||||
}
|
||||
let stale = false
|
||||
const refreshWorktreeName = async () => {
|
||||
try {
|
||||
const response = await client.sendRequest('worktree.show', {
|
||||
worktree: `id:${worktreeId}`
|
||||
})
|
||||
if (stale || !response.ok) {
|
||||
return
|
||||
}
|
||||
const result = (response as RpcSuccess).result as {
|
||||
worktree?: WorktreeDisplayNameSource
|
||||
}
|
||||
const liveName = result.worktree
|
||||
? getLiveWorktreeDisplayName([result.worktree], worktreeId)
|
||||
: null
|
||||
if (liveName) {
|
||||
setWorktreeName((current) => (current === liveName ? current : liveName))
|
||||
}
|
||||
} catch {
|
||||
// Non-fatal: the route param remains a usable label until the next refresh.
|
||||
}
|
||||
}
|
||||
// Why: route params are only an entry hint. The desktop/runtime owns
|
||||
// displayName, including task-generated names that may settle after open.
|
||||
void refreshWorktreeName()
|
||||
const interval = setInterval(() => void refreshWorktreeName(), 3000)
|
||||
return () => {
|
||||
stale = true
|
||||
clearInterval(interval)
|
||||
}
|
||||
}, [client, connState, worktreeId])
|
||||
)
|
||||
|
||||
return worktreeName
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { getLiveWorktreeDisplayName } from './worktree-display-name'
|
||||
|
||||
describe('getLiveWorktreeDisplayName', () => {
|
||||
it('uses the host-list display name for the current worktree', () => {
|
||||
expect(
|
||||
getLiveWorktreeDisplayName(
|
||||
[
|
||||
{ worktreeId: 'wt-1', displayName: 'Old' },
|
||||
{ worktreeId: 'wt-2', displayName: 'Auto Generated Name' }
|
||||
],
|
||||
'wt-2'
|
||||
)
|
||||
).toBe('Auto Generated Name')
|
||||
})
|
||||
|
||||
it('matches worktree.show payloads keyed by id', () => {
|
||||
expect(getLiveWorktreeDisplayName([{ id: 'wt-1', displayName: 'Settled Name' }], 'wt-1')).toBe(
|
||||
'Settled Name'
|
||||
)
|
||||
})
|
||||
|
||||
it('falls back to repo only when the display name is blank', () => {
|
||||
expect(
|
||||
getLiveWorktreeDisplayName([{ worktreeId: 'wt-1', displayName: ' ', repo: 'orca' }], 'wt-1')
|
||||
).toBe('orca')
|
||||
})
|
||||
|
||||
it('ignores snapshots that do not contain the current worktree', () => {
|
||||
expect(getLiveWorktreeDisplayName([{ worktreeId: 'wt-1', displayName: 'Other' }], 'wt-2')).toBe(
|
||||
null
|
||||
)
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,21 @@
|
||||
export type WorktreeDisplayNameSource = {
|
||||
worktreeId?: string
|
||||
id?: string
|
||||
displayName?: string | null
|
||||
repo?: string | null
|
||||
}
|
||||
|
||||
export function getLiveWorktreeDisplayName(
|
||||
worktrees: readonly WorktreeDisplayNameSource[],
|
||||
worktreeId: string
|
||||
): string | null {
|
||||
const worktree = worktrees.find((item) => (item.worktreeId ?? item.id) === worktreeId)
|
||||
if (!worktree) {
|
||||
return null
|
||||
}
|
||||
const displayName = worktree.displayName?.trim()
|
||||
if (displayName) {
|
||||
return displayName
|
||||
}
|
||||
return worktree.repo?.trim() || null
|
||||
}
|
||||
Reference in New Issue
Block a user