mirror of
https://github.com/stablyai/orca.git
synced 2026-10-20 16:03:20 +00:00
fix(agent-map): prioritize attention ring status
This commit is contained in:
@@ -161,7 +161,7 @@ describe('AgentMap', () => {
|
||||
expect(doneNode).toHaveAccessibleName(/unread/)
|
||||
})
|
||||
|
||||
it('glows working agents and their worktree ring only', () => {
|
||||
it('lets attention override working on the worktree glow', () => {
|
||||
const done = card({
|
||||
paneKey: 'done',
|
||||
conversationName: 'Finished agent',
|
||||
@@ -171,9 +171,16 @@ describe('AgentMap', () => {
|
||||
dotState: 'done',
|
||||
finishedAt: NOW - 60_000
|
||||
})
|
||||
const { container } = renderMap([card(), done])
|
||||
const waiting = card({
|
||||
paneKey: 'waiting',
|
||||
conversationName: 'Question agent',
|
||||
bucket: 'attention',
|
||||
dotState: 'waiting'
|
||||
})
|
||||
const { container } = renderMap([card(), waiting, done])
|
||||
|
||||
const workingNode = screen.getByRole('button', { name: /Agent alpha/ })
|
||||
const waitingNode = screen.getByRole('button', { name: /Question agent/ })
|
||||
const doneNode = screen.getByRole('button', { name: /Finished agent/ })
|
||||
const workingRing = screen.getByRole('button', {
|
||||
name: /Open Agent map worktree details/
|
||||
@@ -183,10 +190,16 @@ describe('AgentMap', () => {
|
||||
})
|
||||
|
||||
expect(workingNode.querySelector('[data-agent-map-agent-working-glow]')).toBeInTheDocument()
|
||||
expect(waitingNode.querySelector('[data-agent-map-agent-working-glow]')).not.toBeInTheDocument()
|
||||
expect(doneNode.querySelector('[data-agent-map-agent-working-glow]')).not.toBeInTheDocument()
|
||||
expect(workingRing).toHaveClass('is-working')
|
||||
expect(workingRing).toHaveClass('is-waiting')
|
||||
expect(workingRing).not.toHaveClass('is-working')
|
||||
expect(doneRing).not.toHaveClass('is-working')
|
||||
expect(container.querySelectorAll('[data-agent-map-worktree-working-glow]')).toHaveLength(1)
|
||||
expect(container.querySelector('[data-agent-map-worktree-status-glow]')).toHaveAttribute(
|
||||
'data-worktree-active-status',
|
||||
'waiting'
|
||||
)
|
||||
expect(container.querySelectorAll('[data-agent-map-worktree-status-glow]')).toHaveLength(1)
|
||||
})
|
||||
|
||||
it('keeps glow markup bounded for a large visible worktree', () => {
|
||||
@@ -206,7 +219,7 @@ describe('AgentMap', () => {
|
||||
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('[data-agent-map-worktree-status-glow]')).toHaveLength(1)
|
||||
expect(container.querySelectorAll('filter')).toHaveLength(0)
|
||||
})
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@ import type {
|
||||
AgentMapWorktreeRing
|
||||
} from './agent-map-layout'
|
||||
import { shouldAggregateAgentMapWorktree } from './agent-map-layout'
|
||||
import { agentMapWorktreeActiveStatus } from './agent-map-worktree-active-status'
|
||||
|
||||
type AgentMapWorktreeRingNodeProps = {
|
||||
project: AgentMapProjectRing
|
||||
@@ -187,7 +188,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 activeStatus = agentMapWorktreeActiveStatus(worktree.statusCounts)
|
||||
const aggregate = !selected && shouldAggregateAgentMapWorktree(worktree, zoom, allowAggregation)
|
||||
const agentsByPaneKey = new Map(worktree.agents.map((agent) => [agent.card.paneKey, agent]))
|
||||
|
||||
@@ -204,10 +205,11 @@ export const AgentMapWorktreeRingNode = memo(function AgentMapWorktreeRingNode({
|
||||
}
|
||||
}}
|
||||
>
|
||||
{working ? (
|
||||
{activeStatus ? (
|
||||
<circle
|
||||
className="agent-map-worktree-working-glow"
|
||||
data-agent-map-worktree-working-glow=""
|
||||
className={`agent-map-worktree-status-glow fleet-status-${activeStatus}`}
|
||||
data-agent-map-worktree-status-glow=""
|
||||
data-worktree-active-status={activeStatus}
|
||||
cx={worktree.x}
|
||||
cy={worktree.y}
|
||||
r={worktree.radius}
|
||||
@@ -216,7 +218,7 @@ export const AgentMapWorktreeRingNode = memo(function AgentMapWorktreeRingNode({
|
||||
) : null}
|
||||
<PopoverTrigger asChild>
|
||||
<circle
|
||||
className={`agent-map-worktree-ring${working ? ' is-working' : ''}${selected ? ' is-selected' : ''}${detailsOpen ? ' is-open' : ''}`}
|
||||
className={`agent-map-worktree-ring${activeStatus ? ` is-${activeStatus}` : ''}${selected ? ' is-selected' : ''}${detailsOpen ? ' is-open' : ''}`}
|
||||
data-agent-map-worktree=""
|
||||
data-agent-count={worktree.agents.length}
|
||||
cx={worktree.x}
|
||||
|
||||
@@ -10,17 +10,17 @@ function source(file: string): string {
|
||||
}
|
||||
|
||||
describe('Agent Map glow performance boundary', () => {
|
||||
it('uses one conditional SVG halo per working entity without filter effects', () => {
|
||||
it('uses one conditional SVG halo per active 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-worktree-status-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)
|
||||
const glowRules = css.match(/\.agent-map-(?:worktree-status|agent-working)-glow\s*\{[^}]+\}/gs)
|
||||
|
||||
expect(glowRules).toHaveLength(2)
|
||||
for (const rule of glowRules ?? []) {
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import type { AgentMapStatusCounts } from './agent-map-layout'
|
||||
import { agentMapWorktreeActiveStatus } from './agent-map-worktree-active-status'
|
||||
|
||||
function counts(overrides: Partial<AgentMapStatusCounts> = {}): AgentMapStatusCounts {
|
||||
return { blocked: 0, waiting: 0, working: 0, done: 0, idle: 0, ...overrides }
|
||||
}
|
||||
|
||||
describe('agentMapWorktreeActiveStatus', () => {
|
||||
it('prioritizes attention over working', () => {
|
||||
expect(agentMapWorktreeActiveStatus(counts({ working: 2, waiting: 1 }))).toBe('waiting')
|
||||
expect(agentMapWorktreeActiveStatus(counts({ working: 2, waiting: 1, blocked: 1 }))).toBe(
|
||||
'blocked'
|
||||
)
|
||||
})
|
||||
|
||||
it('uses working only when no agent needs attention', () => {
|
||||
expect(agentMapWorktreeActiveStatus(counts({ working: 1, done: 2 }))).toBe('working')
|
||||
expect(agentMapWorktreeActiveStatus(counts({ done: 2, idle: 1 }))).toBeNull()
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,15 @@
|
||||
import type { AgentMapStatusCounts } from './agent-map-layout'
|
||||
|
||||
export type AgentMapWorktreeActiveStatus = 'blocked' | 'waiting' | 'working'
|
||||
|
||||
export function agentMapWorktreeActiveStatus(
|
||||
counts: AgentMapStatusCounts
|
||||
): AgentMapWorktreeActiveStatus | null {
|
||||
if (counts.blocked > 0) {
|
||||
return 'blocked'
|
||||
}
|
||||
if (counts.waiting > 0) {
|
||||
return 'waiting'
|
||||
}
|
||||
return counts.working > 0 ? 'working' : null
|
||||
}
|
||||
@@ -95,14 +95,25 @@
|
||||
vector-effect: non-scaling-stroke;
|
||||
}
|
||||
|
||||
.agent-map-worktree-working-glow {
|
||||
.agent-map-worktree-status-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-status-glow.fleet-status-blocked {
|
||||
stroke: color-mix(in srgb, var(--color-red-500) 28%, transparent);
|
||||
}
|
||||
|
||||
.agent-map-worktree-status-glow.fleet-status-waiting {
|
||||
stroke: color-mix(in srgb, var(--color-amber-500) 28%, transparent);
|
||||
}
|
||||
|
||||
.agent-map-worktree-status-glow.fleet-status-working {
|
||||
stroke: color-mix(in srgb, var(--color-yellow-500) 28%, transparent);
|
||||
}
|
||||
|
||||
.agent-map-worktree-ring:hover {
|
||||
fill: color-mix(in srgb, var(--card) 72%, transparent);
|
||||
stroke: var(--ring);
|
||||
@@ -131,6 +142,14 @@
|
||||
stroke: var(--color-yellow-500);
|
||||
}
|
||||
|
||||
.agent-map-worktree-ring.is-waiting {
|
||||
stroke: var(--color-amber-500);
|
||||
}
|
||||
|
||||
.agent-map-worktree-ring.is-blocked {
|
||||
stroke: var(--color-red-500);
|
||||
}
|
||||
|
||||
.agent-map-worktree-label {
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
|
||||
Reference in New Issue
Block a user