mirror of
https://github.com/stablyai/orca.git
synced 2026-10-21 00:03:46 +00:00
feat(agent-map): glow working entities
This commit is contained in:
@@ -161,6 +161,55 @@ describe('AgentMap', () => {
|
||||
expect(doneNode).toHaveAccessibleName(/unread/)
|
||||
})
|
||||
|
||||
it('glows working agents and their worktree ring only', () => {
|
||||
const done = card({
|
||||
paneKey: 'done',
|
||||
conversationName: 'Finished agent',
|
||||
worktreeId: 'worktree-done',
|
||||
worktreeName: 'Finished worktree',
|
||||
bucket: 'done',
|
||||
dotState: 'done',
|
||||
finishedAt: NOW - 60_000
|
||||
})
|
||||
const { container } = renderMap([card(), done])
|
||||
|
||||
const workingNode = screen.getByRole('button', { name: /Agent alpha/ })
|
||||
const doneNode = screen.getByRole('button', { name: /Finished agent/ })
|
||||
const workingRing = screen.getByRole('button', {
|
||||
name: /Open Agent map worktree details/
|
||||
})
|
||||
const doneRing = screen.getByRole('button', {
|
||||
name: /Open Finished worktree worktree details/
|
||||
})
|
||||
|
||||
expect(workingNode.querySelector('[data-agent-map-agent-working-glow]')).toBeInTheDocument()
|
||||
expect(doneNode.querySelector('[data-agent-map-agent-working-glow]')).not.toBeInTheDocument()
|
||||
expect(workingRing).toHaveClass('is-working')
|
||||
expect(doneRing).not.toHaveClass('is-working')
|
||||
expect(container.querySelectorAll('[data-agent-map-worktree-working-glow]')).toHaveLength(1)
|
||||
})
|
||||
|
||||
it('keeps glow markup bounded for a large visible worktree', () => {
|
||||
const cards = Array.from({ length: 120 }, (_, index) => {
|
||||
const working = index % 2 === 0
|
||||
return card({
|
||||
paneKey: `pane-${index}`,
|
||||
ptyId: `pty-${index}`,
|
||||
tabId: `tab-${index}`,
|
||||
leafId: `leaf-${index}`,
|
||||
conversationName: `Agent ${index}`,
|
||||
bucket: working ? 'working' : 'done',
|
||||
dotState: working ? 'working' : 'done',
|
||||
finishedAt: working ? null : NOW - 60_000
|
||||
})
|
||||
})
|
||||
const { container } = renderMap(cards, { selectedPaneKey: 'pane-0' })
|
||||
|
||||
expect(container.querySelectorAll('[data-agent-map-agent-working-glow]')).toHaveLength(60)
|
||||
expect(container.querySelectorAll('[data-agent-map-worktree-working-glow]')).toHaveLength(1)
|
||||
expect(container.querySelectorAll('filter')).toHaveLength(0)
|
||||
})
|
||||
|
||||
it('enlarges unread markers when the full fleet is zoomed out', () => {
|
||||
const fleet = Array.from({ length: 72 }, (_, index) =>
|
||||
card({
|
||||
|
||||
@@ -187,6 +187,7 @@ export const AgentMapWorktreeRingNode = memo(function AgentMapWorktreeRingNode({
|
||||
}: AgentMapWorktreeRingNodeProps): React.JSX.Element {
|
||||
const [detailsOpen, setDetailsOpen] = useState(false)
|
||||
const selected = worktree.agents.some((agent) => agent.card.paneKey === selectedPaneKey)
|
||||
const working = worktree.statusCounts.working > 0
|
||||
const aggregate = !selected && shouldAggregateAgentMapWorktree(worktree, zoom, allowAggregation)
|
||||
const agentsByPaneKey = new Map(worktree.agents.map((agent) => [agent.card.paneKey, agent]))
|
||||
|
||||
@@ -203,9 +204,19 @@ export const AgentMapWorktreeRingNode = memo(function AgentMapWorktreeRingNode({
|
||||
}
|
||||
}}
|
||||
>
|
||||
{working ? (
|
||||
<circle
|
||||
className="agent-map-worktree-working-glow"
|
||||
data-agent-map-worktree-working-glow=""
|
||||
cx={worktree.x}
|
||||
cy={worktree.y}
|
||||
r={worktree.radius}
|
||||
aria-hidden="true"
|
||||
/>
|
||||
) : null}
|
||||
<PopoverTrigger asChild>
|
||||
<circle
|
||||
className={`agent-map-worktree-ring${selected ? ' is-selected' : ''}${detailsOpen ? ' is-open' : ''}`}
|
||||
className={`agent-map-worktree-ring${working ? ' is-working' : ''}${selected ? ' is-selected' : ''}${detailsOpen ? ' is-open' : ''}`}
|
||||
data-agent-map-worktree=""
|
||||
data-agent-count={worktree.agents.length}
|
||||
cx={worktree.x}
|
||||
@@ -297,6 +308,14 @@ export const AgentMapWorktreeRingNode = memo(function AgentMapWorktreeRingNode({
|
||||
}}
|
||||
onKeyDown={(event) => onAgentKeyDown(event, agent)}
|
||||
>
|
||||
{agent.status === 'working' ? (
|
||||
<circle
|
||||
className="agent-map-agent-working-glow"
|
||||
data-agent-map-agent-working-glow=""
|
||||
r={agent.radius + 1}
|
||||
aria-hidden="true"
|
||||
/>
|
||||
) : null}
|
||||
<circle className="agent-map-agent-hit" r={Math.max(10, agent.radius + 3)} />
|
||||
<circle className="agent-map-agent-mark" r={agent.radius} />
|
||||
<foreignObject
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
import { readFileSync } from 'node:fs'
|
||||
import { resolve } from 'node:path'
|
||||
import { describe, expect, it } from 'vitest'
|
||||
|
||||
function source(file: string): string {
|
||||
return readFileSync(
|
||||
resolve(process.cwd(), 'src/renderer/src/components/dashboard-popout', file),
|
||||
'utf8'
|
||||
)
|
||||
}
|
||||
|
||||
describe('Agent Map glow performance boundary', () => {
|
||||
it('uses one conditional SVG halo per working entity without filter effects', () => {
|
||||
const component = source('AgentMapWorktreeRingNode.tsx')
|
||||
|
||||
expect(component.match(/data-agent-map-worktree-working-glow/g)).toHaveLength(1)
|
||||
expect(component.match(/data-agent-map-agent-working-glow/g)).toHaveLength(1)
|
||||
expect(component).not.toMatch(/<filter|filter=/)
|
||||
})
|
||||
|
||||
it('keeps glow styling free of animated and filtered paint work', () => {
|
||||
const css = source('agent-map.css')
|
||||
const glowRules = css.match(/\.agent-map-(?:worktree|agent)-working-glow\s*\{[^}]+\}/gs)
|
||||
|
||||
expect(glowRules).toHaveLength(2)
|
||||
for (const rule of glowRules ?? []) {
|
||||
expect(rule).toContain('pointer-events: none')
|
||||
expect(rule).toContain('vector-effect: non-scaling-stroke')
|
||||
expect(rule).not.toMatch(/filter:|animation:|transition:/)
|
||||
}
|
||||
})
|
||||
})
|
||||
@@ -95,6 +95,14 @@
|
||||
vector-effect: non-scaling-stroke;
|
||||
}
|
||||
|
||||
.agent-map-worktree-working-glow {
|
||||
fill: none;
|
||||
pointer-events: none;
|
||||
stroke: color-mix(in srgb, var(--color-yellow-500) 28%, transparent);
|
||||
stroke-width: 9;
|
||||
vector-effect: non-scaling-stroke;
|
||||
}
|
||||
|
||||
.agent-map-worktree-ring:hover {
|
||||
fill: color-mix(in srgb, var(--card) 72%, transparent);
|
||||
stroke: var(--ring);
|
||||
@@ -119,6 +127,10 @@
|
||||
stroke-width: 2.5;
|
||||
}
|
||||
|
||||
.agent-map-worktree-ring.is-working {
|
||||
stroke: var(--color-yellow-500);
|
||||
}
|
||||
|
||||
.agent-map-worktree-label {
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
@@ -187,6 +199,14 @@
|
||||
vector-effect: non-scaling-stroke;
|
||||
}
|
||||
|
||||
.agent-map-agent-working-glow {
|
||||
fill: none;
|
||||
pointer-events: none;
|
||||
stroke: color-mix(in srgb, var(--color-yellow-500) 32%, transparent);
|
||||
stroke-width: 7;
|
||||
vector-effect: non-scaling-stroke;
|
||||
}
|
||||
|
||||
.agent-map-agent-icon {
|
||||
overflow: visible;
|
||||
pointer-events: none;
|
||||
|
||||
Reference in New Issue
Block a user