Revert agent hovercard (#2460)
This commit is contained in:
parent
a6075e06c8
commit
20099eb82e
|
|
@ -1,13 +1,4 @@
|
|||
import { describe, expect, it, vi } from 'vitest'
|
||||
|
||||
const { writeTextMock } = vi.hoisted(() => ({
|
||||
writeTextMock: vi.fn()
|
||||
}))
|
||||
|
||||
vi.mock('electron', () => ({
|
||||
clipboard: { writeText: writeTextMock }
|
||||
}))
|
||||
|
||||
import { buildEditableContextMenuTemplate } from './editable-context-menu'
|
||||
import { richMarkdownContextMenuCommandChannel } from '../../shared/rich-markdown-context-menu'
|
||||
|
||||
|
|
@ -148,31 +139,6 @@ describe('buildEditableContextMenuTemplate', () => {
|
|||
).toEqual([])
|
||||
})
|
||||
|
||||
it('builds a native read-only text menu for selected markdown text and links', () => {
|
||||
const template = buildEditableContextMenuTemplate(
|
||||
contextParams({
|
||||
isEditable: false,
|
||||
linkURL: 'https://example.com/docs',
|
||||
selectionText: 'https://example.com/docs'
|
||||
}),
|
||||
{
|
||||
replaceMisspelling: vi.fn(),
|
||||
send: vi.fn(),
|
||||
session: { addWordToSpellCheckerDictionary: vi.fn() } as unknown as Electron.Session
|
||||
}
|
||||
)
|
||||
|
||||
expect(template.map((item) => item.label ?? item.role ?? item.type)).toEqual([
|
||||
'Copy URL',
|
||||
'separator',
|
||||
'copy',
|
||||
'selectAll'
|
||||
])
|
||||
|
||||
template[0].click?.({} as Electron.MenuItem, {} as Electron.BrowserWindow, {} as KeyboardEvent)
|
||||
expect(writeTextMock).toHaveBeenCalledWith('https://example.com/docs')
|
||||
})
|
||||
|
||||
it('keeps regular text inputs to spelling and native edit actions', () => {
|
||||
const template = buildEditableContextMenuTemplate(
|
||||
contextParams({ formControlType: 'input-text' }),
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
import { clipboard } from 'electron'
|
||||
import {
|
||||
richMarkdownContextMenuCommandChannel,
|
||||
type RichMarkdownContextMenuCommand,
|
||||
|
|
@ -84,34 +83,12 @@ function buildNativeEditMenuTemplate(): Electron.MenuItemConstructorOptions[] {
|
|||
]
|
||||
}
|
||||
|
||||
function buildReadOnlyTextMenuTemplate(
|
||||
params: Electron.ContextMenuParams
|
||||
): Electron.MenuItemConstructorOptions[] {
|
||||
const template: Electron.MenuItemConstructorOptions[] = []
|
||||
if (params.linkURL) {
|
||||
template.push({
|
||||
label: 'Copy URL',
|
||||
click: () => clipboard.writeText(params.linkURL)
|
||||
})
|
||||
}
|
||||
if (params.selectionText) {
|
||||
if (template.length > 0) {
|
||||
template.push({ type: 'separator' })
|
||||
}
|
||||
template.push({ role: 'copy' })
|
||||
}
|
||||
if (template.length > 0) {
|
||||
template.push({ role: 'selectAll' })
|
||||
}
|
||||
return template
|
||||
}
|
||||
|
||||
export function buildEditableContextMenuTemplate(
|
||||
params: Electron.ContextMenuParams,
|
||||
webContents: EditableContextMenuWebContents
|
||||
): Electron.MenuItemConstructorOptions[] {
|
||||
if (!params.isEditable) {
|
||||
return buildReadOnlyTextMenuTemplate(params)
|
||||
return []
|
||||
}
|
||||
|
||||
const suggestions = params.dictionarySuggestions.slice(0, 5)
|
||||
|
|
|
|||
|
|
@ -1,73 +0,0 @@
|
|||
import { renderToStaticMarkup } from 'react-dom/server'
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { TooltipProvider } from '@/components/ui/tooltip'
|
||||
import { DashboardAgentHoverCardContent } from './DashboardAgentHoverCardContent'
|
||||
import type { DashboardAgentRow } from './useDashboardData'
|
||||
|
||||
function makeAgentRow(): DashboardAgentRow {
|
||||
const now = Date.UTC(2026, 4, 20)
|
||||
const paneKey = 'tab-1:11111111-1111-4111-8111-111111111111'
|
||||
const prompt = 'Review **markdown** formatting'
|
||||
|
||||
return {
|
||||
paneKey,
|
||||
agentType: 'codex',
|
||||
state: 'working',
|
||||
startedAt: now - 60_000,
|
||||
tab: {
|
||||
id: 'tab-1',
|
||||
ptyId: null,
|
||||
worktreeId: 'wt-1',
|
||||
title: 'Codex',
|
||||
customTitle: null,
|
||||
color: null,
|
||||
sortOrder: 0,
|
||||
createdAt: now
|
||||
},
|
||||
entry: {
|
||||
state: 'working',
|
||||
prompt,
|
||||
updatedAt: now,
|
||||
stateStartedAt: now - 60_000,
|
||||
agentType: 'codex',
|
||||
paneKey,
|
||||
stateHistory: [{ state: 'working', prompt, startedAt: now - 60_000 }],
|
||||
toolName: 'Bash',
|
||||
toolInput: 'pnpm test',
|
||||
lastAssistantMessage: 'Rendered **markdown** in the hover card.'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
describe('DashboardAgentHoverCardContent', () => {
|
||||
it('renders prompt and assistant markdown in structured sections', () => {
|
||||
const markup = renderToStaticMarkup(
|
||||
<TooltipProvider>
|
||||
<DashboardAgentHoverCardContent
|
||||
agent={makeAgentRow()}
|
||||
dotState="working"
|
||||
prompt="Review **markdown** formatting"
|
||||
isWorking
|
||||
toolName="Bash"
|
||||
toolInput="pnpm test"
|
||||
lastAssistantMessage="Rendered **markdown** in the hover card."
|
||||
headerTimestamp="1m ago"
|
||||
onActivate={() => {}}
|
||||
/>
|
||||
</TooltipProvider>
|
||||
)
|
||||
|
||||
expect(markup).toContain('Prompt')
|
||||
expect(markup).toContain('Current tool')
|
||||
expect(markup).toContain('Latest message')
|
||||
expect(markup).toContain('Copy prompt')
|
||||
expect(markup).toContain('Copy latest message')
|
||||
expect(markup).toContain('data-slot="scroll-area"')
|
||||
expect(markup).toContain('data-slot="scroll-area-viewport"')
|
||||
expect(markup).toContain('whitespace-pre')
|
||||
expect(markup).toContain('[&_pre]:max-w-none')
|
||||
expect(markup).not.toContain('overflow-auto')
|
||||
expect(markup).toContain('<strong>markdown</strong>')
|
||||
expect(markup).toContain('pnpm test')
|
||||
})
|
||||
})
|
||||
|
|
@ -1,218 +0,0 @@
|
|||
import React from 'react'
|
||||
import { ArrowRight, Check, Copy } from 'lucide-react'
|
||||
import { AgentStateDot, type AgentDotState } from '@/components/AgentStateDot'
|
||||
import { AgentIcon } from '@/lib/agent-catalog'
|
||||
import { agentTypeToIconAgent, formatAgentTypeLabel } from '@/lib/agent-status'
|
||||
import CommentMarkdown from '@/components/sidebar/CommentMarkdown'
|
||||
import { WORKTREE_CONTEXT_MENU_SCOPE_ATTR } from '@/components/sidebar/worktree-context-menu-scope'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { ScrollArea } from '@/components/ui/scroll-area'
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip'
|
||||
import type { DashboardAgentRow as DashboardAgentRowData } from './useDashboardData'
|
||||
|
||||
type AgentHoverSectionProps = {
|
||||
title: string
|
||||
children: React.ReactNode
|
||||
copyLabel?: string
|
||||
copied?: boolean
|
||||
onCopy?: (event: React.MouseEvent<HTMLButtonElement>) => void
|
||||
}
|
||||
|
||||
function AgentHoverSection({
|
||||
title,
|
||||
children,
|
||||
copyLabel,
|
||||
copied = false,
|
||||
onCopy
|
||||
}: AgentHoverSectionProps): React.JSX.Element {
|
||||
return (
|
||||
<section className="space-y-1.5">
|
||||
<div className="flex min-w-0 items-center gap-1.5">
|
||||
<div className="text-[11px] font-semibold uppercase tracking-[0.05em] text-muted-foreground">
|
||||
{title}
|
||||
</div>
|
||||
{onCopy && copyLabel && (
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Button
|
||||
type="button"
|
||||
variant="ghost"
|
||||
size="icon-xs"
|
||||
className="size-5 text-muted-foreground hover:text-foreground"
|
||||
onClick={onCopy}
|
||||
onMouseDown={(event) => event.stopPropagation()}
|
||||
aria-label={copyLabel}
|
||||
>
|
||||
{copied ? <Check className="size-3" /> : <Copy className="size-3" />}
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="top" sideOffset={4}>
|
||||
{copied ? 'Copied' : copyLabel}
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
)}
|
||||
</div>
|
||||
<div className="text-[12px] leading-relaxed text-popover-foreground">{children}</div>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
|
||||
type DashboardAgentHoverCardContentProps = {
|
||||
agent: DashboardAgentRowData
|
||||
dotState: AgentDotState
|
||||
prompt: string
|
||||
isWorking: boolean
|
||||
toolName: string
|
||||
toolInput: string
|
||||
lastAssistantMessage: string
|
||||
headerTimestamp: string | null
|
||||
onActivate: (event: React.SyntheticEvent) => void
|
||||
}
|
||||
|
||||
export function DashboardAgentHoverCardContent({
|
||||
agent,
|
||||
dotState,
|
||||
prompt,
|
||||
isWorking,
|
||||
toolName,
|
||||
toolInput,
|
||||
lastAssistantMessage,
|
||||
headerTimestamp,
|
||||
onActivate
|
||||
}: DashboardAgentHoverCardContentProps): React.JSX.Element {
|
||||
const copyResetTimerRef = React.useRef<ReturnType<typeof setTimeout> | null>(null)
|
||||
const [copiedSection, setCopiedSection] = React.useState<'prompt' | 'latest' | null>(null)
|
||||
const agentLabel = formatAgentTypeLabel(agent.agentType)
|
||||
const hasToolDetails = isWorking && (toolName.length > 0 || toolInput.length > 0)
|
||||
const hasBodyDetails = prompt.length > 0 || hasToolDetails || lastAssistantMessage.length > 0
|
||||
const copySectionText = React.useCallback((section: 'prompt' | 'latest', text: string) => {
|
||||
return async (event: React.MouseEvent<HTMLButtonElement>) => {
|
||||
event.stopPropagation()
|
||||
await window.api.ui.writeClipboardText(text)
|
||||
setCopiedSection(section)
|
||||
if (copyResetTimerRef.current) {
|
||||
clearTimeout(copyResetTimerRef.current)
|
||||
}
|
||||
copyResetTimerRef.current = setTimeout(() => setCopiedSection(null), 1200)
|
||||
}
|
||||
}, [])
|
||||
const handleHeaderKeyDown = React.useCallback(
|
||||
(event: React.KeyboardEvent<HTMLDivElement>) => {
|
||||
if (event.key !== 'Enter' && event.key !== ' ') {
|
||||
return
|
||||
}
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
onActivate(event)
|
||||
},
|
||||
[onActivate]
|
||||
)
|
||||
|
||||
React.useEffect(() => {
|
||||
return () => {
|
||||
if (copyResetTimerRef.current) {
|
||||
clearTimeout(copyResetTimerRef.current)
|
||||
}
|
||||
}
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<div
|
||||
className="flex max-h-[min(70vh,520px)] flex-col"
|
||||
onClick={(event) => event.stopPropagation()}
|
||||
onMouseDown={(event) => event.stopPropagation()}
|
||||
onContextMenuCapture={(event) => event.stopPropagation()}
|
||||
{...{ [WORKTREE_CONTEXT_MENU_SCOPE_ATTR]: 'agent-hover-card' }}
|
||||
>
|
||||
<div
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
className="group/header flex h-10 shrink-0 cursor-pointer items-center border-b border-border/60 bg-popover px-3 text-left transition-colors hover:bg-accent/70 focus-visible:outline-none focus-visible:ring-[3px] focus-visible:ring-ring/50"
|
||||
onClick={onActivate}
|
||||
onKeyDown={handleHeaderKeyDown}
|
||||
>
|
||||
<div className="flex min-w-0 w-full items-center gap-2">
|
||||
<AgentStateDot state={dotState} size="md" />
|
||||
<span className="inline-flex size-4 shrink-0 items-center justify-center text-foreground">
|
||||
<AgentIcon agent={agentTypeToIconAgent(agent.agentType)} size={16} />
|
||||
</span>
|
||||
<div className="min-w-0 flex-1 truncate text-sm font-semibold leading-4 text-popover-foreground">
|
||||
{agentLabel}
|
||||
</div>
|
||||
{agent.entry.interrupted && (
|
||||
<span className="shrink-0 rounded-sm bg-destructive/15 px-1.5 py-0.5 text-[10px] font-medium leading-none text-destructive">
|
||||
interrupted
|
||||
</span>
|
||||
)}
|
||||
<span className="ml-auto grid shrink-0 grid-cols-1 grid-rows-1 items-center justify-items-end">
|
||||
{headerTimestamp && (
|
||||
<span className="[grid-area:1/1] text-xs font-normal text-muted-foreground transition-opacity group-hover/header:opacity-0">
|
||||
{headerTimestamp}
|
||||
</span>
|
||||
)}
|
||||
<span className="[grid-area:1/1] inline-flex items-center gap-1 text-xs font-medium text-muted-foreground opacity-0 transition-opacity group-hover/header:opacity-100">
|
||||
<ArrowRight className="size-3" />
|
||||
Open
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<ScrollArea
|
||||
className="min-h-0 max-h-[calc(min(70vh,520px)-2.5rem)] flex-1"
|
||||
viewportClassName="max-h-[calc(min(70vh,520px)-2.5rem)]"
|
||||
type="auto"
|
||||
scrollbars="both"
|
||||
>
|
||||
<div className="min-w-full space-y-3 px-3 py-3 [&_pre]:max-w-none [&_table]:min-w-max">
|
||||
{prompt && (
|
||||
<AgentHoverSection
|
||||
title="Prompt"
|
||||
copyLabel="Copy prompt"
|
||||
copied={copiedSection === 'prompt'}
|
||||
onCopy={copySectionText('prompt', prompt)}
|
||||
>
|
||||
<CommentMarkdown
|
||||
content={prompt}
|
||||
variant="document"
|
||||
className="text-[12px] leading-relaxed"
|
||||
/>
|
||||
</AgentHoverSection>
|
||||
)}
|
||||
{hasToolDetails && (
|
||||
<AgentHoverSection title="Current tool">
|
||||
<div className="space-y-1.5">
|
||||
{toolName && (
|
||||
<code className="rounded bg-accent px-1.5 py-0.5 font-mono text-[11px] text-accent-foreground">
|
||||
{toolName}
|
||||
</code>
|
||||
)}
|
||||
{toolInput && (
|
||||
<pre className="whitespace-pre rounded-md bg-accent p-2 font-mono text-[11px] leading-snug text-accent-foreground">
|
||||
{toolInput}
|
||||
</pre>
|
||||
)}
|
||||
</div>
|
||||
</AgentHoverSection>
|
||||
)}
|
||||
{lastAssistantMessage && (
|
||||
<AgentHoverSection
|
||||
title="Latest message"
|
||||
copyLabel="Copy latest message"
|
||||
copied={copiedSection === 'latest'}
|
||||
onCopy={copySectionText('latest', lastAssistantMessage)}
|
||||
>
|
||||
<CommentMarkdown
|
||||
content={lastAssistantMessage}
|
||||
variant="document"
|
||||
className="text-[12px] leading-relaxed"
|
||||
/>
|
||||
</AgentHoverSection>
|
||||
)}
|
||||
{!hasBodyDetails && (
|
||||
<div className="text-xs text-muted-foreground">No agent details yet.</div>
|
||||
)}
|
||||
</div>
|
||||
</ScrollArea>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
@ -5,8 +5,6 @@ import { AgentStateDot, agentStateLabel, type AgentDotState } from '@/components
|
|||
import { AgentIcon } from '@/lib/agent-catalog'
|
||||
import { agentTypeToIconAgent, formatAgentTypeLabel } from '@/lib/agent-status'
|
||||
import CommentMarkdown from '@/components/sidebar/CommentMarkdown'
|
||||
import { HoverCard, HoverCardContent, HoverCardTrigger } from '@/components/ui/hover-card'
|
||||
import { DashboardAgentHoverCardContent } from './DashboardAgentHoverCardContent'
|
||||
import type { AgentStatusState } from '../../../../shared/agent-status-types'
|
||||
import type { DashboardAgentRow as DashboardAgentRowData } from './useDashboardData'
|
||||
|
||||
|
|
@ -117,7 +115,6 @@ const DashboardAgentRow = React.memo(function DashboardAgentRow({
|
|||
hideExpand = false
|
||||
}: Props) {
|
||||
const [expanded, setExpanded] = useState(false)
|
||||
const [hoverCardOpen, setHoverCardOpen] = useState(false)
|
||||
// Why: stop propagation so clicking the X doesn't also fire the worktree
|
||||
// card's click handler (which navigates away from the dashboard).
|
||||
const handleDismiss = useCallback(
|
||||
|
|
@ -160,25 +157,16 @@ const DashboardAgentRow = React.memo(function DashboardAgentRow({
|
|||
},
|
||||
[onActivate, agent.tab.id, agent.paneKey]
|
||||
)
|
||||
const handleHoverCardActivate = useCallback(
|
||||
(e: React.SyntheticEvent) => {
|
||||
e.stopPropagation()
|
||||
setHoverCardOpen(false)
|
||||
onActivate(agent.tab.id, agent.paneKey)
|
||||
},
|
||||
[onActivate, agent.tab.id, agent.paneKey]
|
||||
)
|
||||
const startedAt = agent.startedAt > 0 ? agent.startedAt : null
|
||||
const doneAt = lastEnteredDoneAt(agent)
|
||||
const prompt = agent.entry.prompt.trim()
|
||||
const dotState = asDotState(agent.state)
|
||||
// Why: `agent.entry.prompt` is normalized to '' when the prompt is unknown
|
||||
// (fresh agent, missing telemetry). Rendering the row with an empty primary
|
||||
// slot would collapse the text column and leave the row with no human-
|
||||
// readable label — just a state dot and icon. Fall back to the state label
|
||||
// ("Working", "Done", "Waiting", …) so every row is identifiable at a
|
||||
// glance.
|
||||
const displayLabel = prompt || agentStateLabel(dotState)
|
||||
const displayLabel = prompt || agentStateLabel(asDotState(agent.state))
|
||||
// Why: the tool row describes what the agent is *currently* doing; once it
|
||||
// leaves working, that line goes stale and misleads (a done row showing
|
||||
// "Bash: pnpm test" reads as if the command is still running). Gate tool
|
||||
|
|
@ -202,17 +190,11 @@ const DashboardAgentRow = React.memo(function DashboardAgentRow({
|
|||
if (doneAt !== null) {
|
||||
tsParts.push(`done ${formatTimeAgo(doneAt, now)}`)
|
||||
}
|
||||
const headerTimestamp =
|
||||
doneAt !== null
|
||||
? formatTimeAgo(doneAt, now)
|
||||
: startedAt !== null
|
||||
? formatTimeAgo(startedAt, now)
|
||||
: null
|
||||
|
||||
const row = (
|
||||
return (
|
||||
// Why: NOT role="button" / tabIndex={0}. The row contains real <button>
|
||||
// children (dismiss X, expand chevron) and is itself the hover-card
|
||||
// trigger — nesting them inside an outer
|
||||
// children (dismiss X, expand chevron) and tooltip triggers that forward
|
||||
// button semantics to their children — nesting them inside an outer
|
||||
// role=button violates ARIA's "no interactive content inside interactive
|
||||
// content" rule and breaks keyboard/AT navigation. Keyboard users reach
|
||||
// the agent via the child buttons and the tab switcher; the outer <div>
|
||||
|
|
@ -229,6 +211,7 @@ const DashboardAgentRow = React.memo(function DashboardAgentRow({
|
|||
// active-state pattern) so the lift is symmetric across themes.
|
||||
'cursor-pointer rounded-sm hover:bg-black/[0.06] dark:hover:bg-accent/30'
|
||||
)}
|
||||
title={tsParts.length > 0 ? tsParts.join(' • ') : undefined}
|
||||
>
|
||||
<div className="flex items-center gap-1.5">
|
||||
{/* Why: state indicator lives in the leading gutter so the user's
|
||||
|
|
@ -240,9 +223,9 @@ const DashboardAgentRow = React.memo(function DashboardAgentRow({
|
|||
overpowering the prompt text. */}
|
||||
<span
|
||||
className="inline-flex shrink-0 items-center justify-center"
|
||||
aria-label={agent.entry.interrupted ? 'Interrupted' : agentStateLabel(dotState)}
|
||||
title={agent.entry.interrupted ? 'Interrupted' : agentStateLabel(asDotState(agent.state))}
|
||||
>
|
||||
<AgentStateDot state={dotState} size={stateDotSize} />
|
||||
<AgentStateDot state={asDotState(agent.state)} size={stateDotSize} />
|
||||
</span>
|
||||
{/* Why: identity (Claude/Codex/Gemini/…) sits inline with the prompt
|
||||
so the reader gets "state → who → what they said" left-to-right
|
||||
|
|
@ -251,7 +234,7 @@ const DashboardAgentRow = React.memo(function DashboardAgentRow({
|
|||
them — keeping the icon only on the prompt row lets the sub-rows
|
||||
indent under the prompt text cleanly. */}
|
||||
{!hideIdentityIcon && (
|
||||
<span className="inline-flex shrink-0" aria-label={formatAgentTypeLabel(agent.agentType)}>
|
||||
<span className="inline-flex shrink-0" title={formatAgentTypeLabel(agent.agentType)}>
|
||||
<AgentIcon agent={agentTypeToIconAgent(agent.agentType)} size={14} />
|
||||
</span>
|
||||
)}
|
||||
|
|
@ -413,7 +396,9 @@ const DashboardAgentRow = React.memo(function DashboardAgentRow({
|
|||
<Wrench className="size-2.5 shrink-0" />
|
||||
<code className="shrink-0 font-mono text-[10px]">{toolName}</code>
|
||||
{!expanded && toolInput && (
|
||||
<span className="min-w-0 truncate text-muted-foreground/60">{toolInput}</span>
|
||||
<span className="min-w-0 truncate text-muted-foreground/60" title={toolInput}>
|
||||
{toolInput}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
{/* Why: grid-rows [0fr]→[1fr] is the CSS-only height animation
|
||||
|
|
@ -474,6 +459,7 @@ const DashboardAgentRow = React.memo(function DashboardAgentRow({
|
|||
!expanded &&
|
||||
'truncate whitespace-nowrap [&_*]:inline [&_*]:!whitespace-nowrap [&_*]:!m-0 [&_*]:!p-0 [&_ul]:list-none [&_ol]:list-none [&_br]:hidden'
|
||||
)}
|
||||
title={!expanded ? lastAssistantMessage : undefined}
|
||||
/>
|
||||
) : (
|
||||
!expanded && (
|
||||
|
|
@ -482,35 +468,6 @@ const DashboardAgentRow = React.memo(function DashboardAgentRow({
|
|||
)}
|
||||
</div>
|
||||
)
|
||||
|
||||
return (
|
||||
<HoverCard
|
||||
open={hoverCardOpen}
|
||||
onOpenChange={setHoverCardOpen}
|
||||
openDelay={250}
|
||||
closeDelay={120}
|
||||
>
|
||||
<HoverCardTrigger asChild>{row}</HoverCardTrigger>
|
||||
<HoverCardContent
|
||||
side="right"
|
||||
align="start"
|
||||
sideOffset={10}
|
||||
className="w-96 max-w-[calc(100vw-2rem)] p-0"
|
||||
>
|
||||
<DashboardAgentHoverCardContent
|
||||
agent={agent}
|
||||
dotState={dotState}
|
||||
prompt={prompt}
|
||||
isWorking={isWorking}
|
||||
toolName={toolName}
|
||||
toolInput={toolInput}
|
||||
lastAssistantMessage={lastAssistantMessage}
|
||||
headerTimestamp={headerTimestamp}
|
||||
onActivate={handleHoverCardActivate}
|
||||
/>
|
||||
</HoverCardContent>
|
||||
</HoverCard>
|
||||
)
|
||||
})
|
||||
|
||||
export default DashboardAgentRow
|
||||
|
|
|
|||
|
|
@ -39,7 +39,6 @@ import { VIRTUALIZED_SCROLL_ANCHOR_RECORD_EVENT } from '@/hooks/useVirtualizedSc
|
|||
import { getLineageRenderInfo } from './worktree-list-groups'
|
||||
import { getWorkspaceStatus, getWorkspaceStatusVisualMeta } from './workspace-status'
|
||||
import { WorktreeOpenInSubMenu } from './WorktreeOpenInMenu'
|
||||
import { WORKTREE_CONTEXT_MENU_SCOPE_ATTR } from './worktree-context-menu-scope'
|
||||
|
||||
type Props = {
|
||||
worktree: Worktree
|
||||
|
|
@ -51,6 +50,7 @@ type Props = {
|
|||
}
|
||||
|
||||
const CLOSE_ALL_CONTEXT_MENUS_EVENT = 'orca-close-all-context-menus'
|
||||
const WORKTREE_CONTEXT_MENU_SCOPE_ATTR = 'data-worktree-context-menu-scope'
|
||||
const CONTEXT_MENU_CLICK_SUPPRESSION_MS = 500
|
||||
|
||||
function shouldIgnoreNestedWorktreeContextMenuScope(
|
||||
|
|
@ -427,10 +427,6 @@ const WorktreeContextMenu = React.memo(function WorktreeContextMenu({
|
|||
>
|
||||
{!isMultiContext && (
|
||||
<>
|
||||
<DropdownMenuItem onSelect={handleRename} disabled={isDeleting}>
|
||||
<Pencil className="size-3.5" />
|
||||
Edit details
|
||||
</DropdownMenuItem>
|
||||
<WorktreeOpenInSubMenu
|
||||
worktreePath={worktree.path}
|
||||
connectionId={repo?.connectionId ?? null}
|
||||
|
|
@ -507,6 +503,12 @@ const WorktreeContextMenu = React.memo(function WorktreeContextMenu({
|
|||
</DropdownMenuRadioGroup>
|
||||
</DropdownMenuSubContent>
|
||||
</DropdownMenuSub>
|
||||
{!isMultiContext && (
|
||||
<DropdownMenuItem onSelect={handleRename} disabled={isDeleting}>
|
||||
<Pencil className="size-3.5" />
|
||||
Update
|
||||
</DropdownMenuItem>
|
||||
)}
|
||||
<DropdownMenuSeparator />
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
|
|
|
|||
|
|
@ -1 +0,0 @@
|
|||
export const WORKTREE_CONTEXT_MENU_SCOPE_ATTR = 'data-worktree-context-menu-scope'
|
||||
|
|
@ -8,7 +8,6 @@ function ScrollArea({
|
|||
viewportClassName,
|
||||
viewportRef,
|
||||
viewportTabIndex,
|
||||
scrollbars = 'vertical',
|
||||
children,
|
||||
...props
|
||||
}: React.ComponentProps<typeof ScrollAreaPrimitive.Root> & {
|
||||
|
|
@ -16,7 +15,6 @@ function ScrollArea({
|
|||
viewportRef?: React.Ref<HTMLDivElement>
|
||||
/** Set e.g. -1 so the viewport can receive programmatic focus (explorer keyboard shortcuts after inline rename). */
|
||||
viewportTabIndex?: number
|
||||
scrollbars?: 'vertical' | 'horizontal' | 'both'
|
||||
}) {
|
||||
return (
|
||||
<ScrollAreaPrimitive.Root
|
||||
|
|
@ -35,10 +33,7 @@ function ScrollArea({
|
|||
>
|
||||
{children}
|
||||
</ScrollAreaPrimitive.Viewport>
|
||||
{(scrollbars === 'vertical' || scrollbars === 'both') && <ScrollBar />}
|
||||
{(scrollbars === 'horizontal' || scrollbars === 'both') && (
|
||||
<ScrollBar orientation="horizontal" />
|
||||
)}
|
||||
<ScrollBar />
|
||||
<ScrollAreaPrimitive.Corner />
|
||||
</ScrollAreaPrimitive.Root>
|
||||
)
|
||||
|
|
|
|||
Loading…
Reference in New Issue