hermes-agent/apps/desktop/src/hooks/use-worktree-info.ts
Brooklyn Nicholson e90672696e feat(desktop): worktree-aware sidebar grouping + composer/sidebar UX fixes
Group recents as parent-repo → worktree → sessions using local git
metadata (probed over IPC, with a path-name heuristic fallback for
remote backends). Single-worktree repos collapse to one level. Sessions
order by creation time and never reshuffle on new messages.

Also: fuse the status stack to the composer border, restore icon actions
in the queue panel, fix sidebar label truncation and drag styling, hide
sticky-message attachments while pinned, and bump the terminal font.
2026-06-12 18:18:39 -05:00

68 lines
2.1 KiB
TypeScript

import { useEffect, useMemo, useRef, useState } from 'react'
import { uniqueCwds, type WorktreeResolver } from '@/app/chat/sidebar/workspace-groups'
import type { HermesWorktreeInfo } from '@/global'
import type { SessionInfo } from '@/hermes'
import { desktopFsCacheKey, desktopWorktrees } from '@/lib/desktop-fs'
type WorktreeMap = Record<string, HermesWorktreeInfo | null>
/**
* Probe the local filesystem for the git-worktree identity of each session cwd
* and return a resolver the grouping uses to build `parent → worktree`. Results
* are cached per cwd (and reset when the backend connection changes), so a probe
* runs once per directory. Unresolved cwds (probe pending, remote backend, or
* non-git dirs) fall back to the path-name heuristic in `workspaceTreeFor`.
*/
export function useWorktreeInfo(sessions: SessionInfo[], enabled: boolean): WorktreeResolver {
const [map, setMap] = useState<WorktreeMap>({})
const cacheRef = useRef<{ data: WorktreeMap; key: string }>({ data: {}, key: '' })
useEffect(() => {
if (!enabled) {
return
}
const key = desktopFsCacheKey()
if (cacheRef.current.key !== key) {
cacheRef.current = { data: {}, key }
setMap({})
}
const missing = uniqueCwds(sessions).filter(cwd => !(cwd in cacheRef.current.data))
if (!missing.length) {
return
}
let cancelled = false
void desktopWorktrees(missing)
.then(result => {
if (cancelled) {
return
}
// Record every probed cwd (null when absent) so we never re-probe it.
const next: WorktreeMap = { ...cacheRef.current.data }
for (const cwd of missing) {
next[cwd] = result[cwd] ?? null
}
cacheRef.current = { data: next, key }
setMap(next)
})
.catch(() => {
// Bridge unavailable / probe failed — leave cwds unresolved so the
// heuristic fallback handles them.
})
return () => {
cancelled = true
}
}, [sessions, enabled])
return useMemo<WorktreeResolver>(() => (cwd: string) => map[cwd], [map])
}