mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-06-10 08:32:09 +00:00
refactor(desktop): tighten right-rail tab close API
Promote closeRightRailTab/closeActiveRightRailTab as the single public entry point. Drops the activeTabRef + handleCloseDocument indirection in ChatPreviewRail, the unused $rightRailHasContent atom, and the legacy dismissFilePreviewTarget alias. -70 LOC.
This commit is contained in:
parent
dda3894523
commit
c9987f1e22
4 changed files with 23 additions and 93 deletions
|
|
@ -1,5 +1,5 @@
|
|||
import { useStore } from '@nanostores/react'
|
||||
import { type MouseEvent, useCallback, useEffect, useMemo, useRef } from 'react'
|
||||
import { useEffect, useMemo } from 'react'
|
||||
|
||||
import type { SetTitlebarToolGroup } from '@/app/shell/titlebar-controls'
|
||||
import { X } from '@/lib/icons'
|
||||
|
|
@ -14,9 +14,8 @@ import {
|
|||
$filePreviewTabs,
|
||||
$previewReloadRequest,
|
||||
$previewTarget,
|
||||
closeFilePreviewTab,
|
||||
dismissPreviewTarget,
|
||||
type FilePreviewTab,
|
||||
closeActiveRightRailTab,
|
||||
closeRightRailTab,
|
||||
type PreviewTarget
|
||||
} from '@/store/preview'
|
||||
|
||||
|
|
@ -39,21 +38,16 @@ interface ChatPreviewRailProps {
|
|||
}
|
||||
|
||||
interface RailTab {
|
||||
closeLabel: string
|
||||
id: RightRailTabId
|
||||
label: string
|
||||
target: PreviewTarget
|
||||
}
|
||||
|
||||
function previewTabLabel(target: PreviewTarget): string {
|
||||
function tabLabelFor(target: PreviewTarget): string {
|
||||
const value = target.label || target.path || target.source || target.url
|
||||
const parts = value.split(/[\\/]/).filter(Boolean)
|
||||
const tail = value.split(/[\\/]/).filter(Boolean).at(-1)
|
||||
|
||||
return parts.at(-1) || value || 'Preview'
|
||||
}
|
||||
|
||||
function tabLabel(tab: FilePreviewTab): string {
|
||||
return previewTabLabel(tab.target)
|
||||
return tail || value || 'Preview'
|
||||
}
|
||||
|
||||
export function ChatPreviewRail({ onRestartServer, setTitlebarToolGroup }: ChatPreviewRailProps) {
|
||||
|
|
@ -64,30 +58,13 @@ export function ChatPreviewRail({ onRestartServer, setTitlebarToolGroup }: ChatP
|
|||
|
||||
const tabs = useMemo<readonly RailTab[]>(
|
||||
() => [
|
||||
...(previewTarget
|
||||
? [
|
||||
{
|
||||
closeLabel: 'Close preview',
|
||||
id: RIGHT_RAIL_PREVIEW_TAB_ID,
|
||||
label: 'Preview',
|
||||
target: previewTarget
|
||||
} satisfies RailTab
|
||||
]
|
||||
: []),
|
||||
...filePreviewTabs.map(tab => ({
|
||||
closeLabel: `Close ${tabLabel(tab)}`,
|
||||
id: tab.id,
|
||||
label: tabLabel(tab),
|
||||
target: tab.target
|
||||
}))
|
||||
...(previewTarget ? [{ id: RIGHT_RAIL_PREVIEW_TAB_ID, label: 'Preview', target: previewTarget } as RailTab] : []),
|
||||
...filePreviewTabs.map(({ id, target }) => ({ id, label: tabLabelFor(target), target }) as RailTab)
|
||||
],
|
||||
[filePreviewTabs, previewTarget]
|
||||
)
|
||||
|
||||
const activeTab = tabs.find(tab => tab.id === activeTabId) ?? tabs[0]
|
||||
// Read-by-ref so close handlers stay reference-stable across renders.
|
||||
const activeTabRef = useRef<RailTab | undefined>(activeTab)
|
||||
activeTabRef.current = activeTab
|
||||
|
||||
useEffect(() => {
|
||||
if (activeTab && activeTab.id !== activeTabId) {
|
||||
|
|
@ -95,32 +72,6 @@ export function ChatPreviewRail({ onRestartServer, setTitlebarToolGroup }: ChatP
|
|||
}
|
||||
}, [activeTab, activeTabId])
|
||||
|
||||
const closeRailTab = useCallback((tab: RailTab) => {
|
||||
if (tab.id === RIGHT_RAIL_PREVIEW_TAB_ID) {
|
||||
dismissPreviewTarget()
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
closeFilePreviewTab(tab.id)
|
||||
}, [])
|
||||
|
||||
// Stable: PreviewPane lists onClose in a useEffect dep array that pushes
|
||||
// titlebar tools. A fresh closure every render → setTitlebarToolGroup every
|
||||
// render → DesktopController setState → re-render → ∞.
|
||||
const handleCloseDocument = useCallback(() => {
|
||||
const tab = activeTabRef.current
|
||||
|
||||
if (tab) {
|
||||
closeRailTab(tab)
|
||||
}
|
||||
}, [closeRailTab])
|
||||
|
||||
const closeTab = (event: MouseEvent, tab: RailTab) => {
|
||||
event.stopPropagation()
|
||||
closeRailTab(tab)
|
||||
}
|
||||
|
||||
if (!activeTab) {
|
||||
return null
|
||||
}
|
||||
|
|
@ -158,13 +109,13 @@ export function ChatPreviewRail({ onRestartServer, setTitlebarToolGroup }: ChatP
|
|||
{tab.label}
|
||||
</button>
|
||||
<button
|
||||
aria-label={tab.closeLabel}
|
||||
aria-label={`Close ${tab.label}`}
|
||||
className={cn(
|
||||
'mr-1.5 hidden size-4 shrink-0 place-items-center rounded-sm text-muted-foreground/55 transition-colors hover:bg-accent hover:text-foreground focus-visible:grid group-hover/tab:grid',
|
||||
active && 'grid'
|
||||
)}
|
||||
onClick={event => closeTab(event, tab)}
|
||||
title={tab.closeLabel}
|
||||
onClick={() => closeRightRailTab(tab.id)}
|
||||
title={`Close ${tab.label}`}
|
||||
type="button"
|
||||
>
|
||||
<X className="size-3" />
|
||||
|
|
@ -177,7 +128,7 @@ export function ChatPreviewRail({ onRestartServer, setTitlebarToolGroup }: ChatP
|
|||
<div className="min-h-0 flex-1 overflow-hidden">
|
||||
<PreviewPane
|
||||
embedded
|
||||
onClose={handleCloseDocument}
|
||||
onClose={closeActiveRightRailTab}
|
||||
onRestartServer={isPreview ? onRestartServer : undefined}
|
||||
reloadRequest={previewReloadRequest}
|
||||
setTitlebarToolGroup={setTitlebarToolGroup}
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ import {
|
|||
SIDEBAR_MAX_WIDTH,
|
||||
unpinSession
|
||||
} from '../store/layout'
|
||||
import { $filePreviewTarget, $previewTarget, dismissFilePreviewTarget, dismissPreviewTarget } from '../store/preview'
|
||||
import { $filePreviewTarget, $previewTarget, closeActiveRightRailTab } from '../store/preview'
|
||||
import {
|
||||
$activeSessionId,
|
||||
$currentCwd,
|
||||
|
|
@ -138,18 +138,6 @@ export function DesktopController() {
|
|||
}, [chatOpen, filePreviewTarget, previewTarget])
|
||||
|
||||
useEffect(() => {
|
||||
const closePreview = () => {
|
||||
if ($filePreviewTarget.get()) {
|
||||
dismissFilePreviewTarget()
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
if ($previewTarget.get()) {
|
||||
dismissPreviewTarget()
|
||||
}
|
||||
}
|
||||
|
||||
const onKeyDown = (event: KeyboardEvent) => {
|
||||
if (!$filePreviewTarget.get() && !$previewTarget.get()) {
|
||||
return
|
||||
|
|
@ -158,11 +146,11 @@ export function DesktopController() {
|
|||
if ((event.metaKey || event.ctrlKey) && !event.altKey && !event.shiftKey && event.key.toLowerCase() === 'w') {
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
closePreview()
|
||||
closeActiveRightRailTab()
|
||||
}
|
||||
}
|
||||
|
||||
const unsubscribe = window.hermesDesktop?.onClosePreviewRequested?.(closePreview)
|
||||
const unsubscribe = window.hermesDesktop?.onClosePreviewRequested?.(closeActiveRightRailTab)
|
||||
|
||||
window.addEventListener('keydown', onKeyDown, { capture: true })
|
||||
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ import {
|
|||
$sessionPreviewRegistry,
|
||||
beginPreviewServerRestart,
|
||||
clearSessionPreviewRegistry,
|
||||
dismissFilePreviewTarget,
|
||||
closeActiveRightRailTab,
|
||||
dismissPreviewTarget,
|
||||
getSessionPreviewRecord,
|
||||
type PreviewTarget,
|
||||
|
|
@ -114,7 +114,7 @@ describe('preview store', () => {
|
|||
expect($previewTarget.get()).toEqual(withRenderMode(preview, 'preview'))
|
||||
expect(getSessionPreviewRecord('session-1')?.normalized).toEqual(withRenderMode(preview, 'preview'))
|
||||
|
||||
dismissFilePreviewTarget()
|
||||
closeActiveRightRailTab()
|
||||
|
||||
expect($filePreviewTarget.get()).toBeNull()
|
||||
expect($previewTarget.get()).toEqual(withRenderMode(preview, 'preview'))
|
||||
|
|
|
|||
|
|
@ -58,9 +58,6 @@ export const $filePreviewTarget = computed([$filePreviewTabs, $rightRailActiveTa
|
|||
|
||||
return tabs.find(tab => tab.id === activeTabId)?.target ?? null
|
||||
})
|
||||
export const $rightRailHasContent = computed([$previewTarget, $filePreviewTabs], (target, tabs) =>
|
||||
Boolean(target || tabs.length)
|
||||
)
|
||||
export const $previewReloadRequest = atom(0)
|
||||
export const $previewServerRestart = atom<PreviewServerRestart | null>(null)
|
||||
export const $previewServerRestartStatus = computed($previewServerRestart, restart => restart?.status ?? 'idle')
|
||||
|
|
@ -368,7 +365,7 @@ export function dismissPreviewTarget() {
|
|||
}
|
||||
}
|
||||
|
||||
export function closeFilePreviewTab(tabId: RightRailTabId) {
|
||||
function closeFilePreviewTab(tabId: RightRailTabId) {
|
||||
if (!tabId.startsWith('file:')) {
|
||||
return
|
||||
}
|
||||
|
|
@ -389,14 +386,8 @@ export function closeFilePreviewTab(tabId: RightRailTabId) {
|
|||
}
|
||||
}
|
||||
|
||||
export function dismissFilePreviewTarget() {
|
||||
closeFilePreviewTab($rightRailActiveTabId.get())
|
||||
}
|
||||
|
||||
export function closeActiveRightRailTab() {
|
||||
const activeTabId = $rightRailActiveTabId.get()
|
||||
|
||||
if (activeTabId === RIGHT_RAIL_PREVIEW_TAB_ID) {
|
||||
export function closeRightRailTab(tabId: RightRailTabId) {
|
||||
if (tabId === RIGHT_RAIL_PREVIEW_TAB_ID) {
|
||||
if ($previewTarget.get()) {
|
||||
dismissPreviewTarget()
|
||||
}
|
||||
|
|
@ -404,11 +395,11 @@ export function closeActiveRightRailTab() {
|
|||
return
|
||||
}
|
||||
|
||||
if (activeTabId.startsWith('file:')) {
|
||||
closeFilePreviewTab(activeTabId)
|
||||
}
|
||||
closeFilePreviewTab(tabId)
|
||||
}
|
||||
|
||||
export const closeActiveRightRailTab = () => closeRightRailTab($rightRailActiveTabId.get())
|
||||
|
||||
export function clearSessionPreviewRegistry() {
|
||||
$sessionPreviewRegistry.set({})
|
||||
setPreviewTarget(null)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue