refactor(activity): indicate active scope filters

Display visual indicator and updated aria-label in the options menu when
scope filters are applied. Improves visibility of the current filter state
and enhances pointer event handling for action buttons.
This commit is contained in:
Jinjing
2026-09-05 20:40:46 -07:00
parent 9a22d46d88
commit bfdd5fa054
11 changed files with 50 additions and 10 deletions
@@ -5,6 +5,7 @@ import { createRoot, type Root } from 'react-dom/client'
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import { TooltipProvider } from '@/components/ui/tooltip'
import { useAppStore } from '@/store'
import { ActivityThreadOptionsMenu } from './ActivityPrototypePage'
import type { ActivityGroupBy } from './activity-thread-types'
@@ -46,6 +47,7 @@ describe('ActivityThreadOptionsMenu', () => {
let root: Root
beforeEach(() => {
useAppStore.setState({ agentsVisibleHostIds: null, agentsFilterRepoIds: [] })
container = document.createElement('div')
document.body.appendChild(container)
root = createRoot(container)
@@ -57,6 +59,20 @@ describe('ActivityThreadOptionsMenu', () => {
document.body.replaceChildren()
})
it('exposes an active persisted scope visually and in the trigger label', async () => {
useAppStore.setState({ agentsVisibleHostIds: ['local'] })
await act(async () => {
root.render(<Harness />)
})
const trigger = container.querySelector<HTMLButtonElement>(
'button[aria-label="Thread list options, filters active"]'
)
expect(trigger).not.toBeNull()
expect(trigger?.querySelector('[data-scope-filter-dot]')).not.toBeNull()
})
it('opens without recursively updating composed Radix trigger refs', async () => {
await act(async () => {
root.render(<Harness />)
@@ -191,7 +207,7 @@ describe('ActivityThreadOptionsMenu', () => {
expect(document.body.textContent).toContain(
'Filters the activity list to show only threads with unread updates.'
)
expect(unreadItem?.querySelector('svg.lucide-check')).toBeNull()
expect(document.querySelector('[data-unread-dot]')).toBeNull()
})
it('renders show child agents checkbox when onShowChildAgentsChange is provided', async () => {
@@ -96,9 +96,7 @@ function evictPersistedStatuses(identities: readonly AgentStatusCacheIdentity[])
}
}
/**
* Clear a single activity thread silently (no toast notification).
*/
/** Clear one completed thread immediately; bulk clearing owns the undo toast. */
export function clearActivityThread(thread: AgentPaneThread): boolean {
const state = useAppStore.getState()
const plan = planClearCompletedActivity([thread], state)
@@ -165,6 +165,8 @@ describe('ActivityThreadHoverCard and ActivityThreadRow', () => {
)
expect(clearButton).not.toBeNull()
expect(clearButton?.className).toContain('focus-visible:opacity-100')
expect(clearButton?.className).toContain('can-hover:pointer-events-none')
expect(clearButton?.className).toContain('can-hover:group-hover:pointer-events-auto')
expect(clearButton?.className).not.toContain('invisible')
act(() => clearButton?.click())
@@ -80,6 +80,12 @@ export function ActivityThreadOptionsMenu({
}): React.JSX.Element {
const skipCloseAutoFocusRef = React.useRef(false)
const scopeFilterActive = useActivityScopeFilterActive()
const optionsLabel = scopeFilterActive
? translate(
'auto.components.activity.ActivityPrototypePage.threadListOptionsFiltered',
'Thread list options, filters active'
)
: translate('auto.components.activity.ActivityPrototypePage.db8a1878b5', 'Thread list options')
return (
<DropdownMenu>
@@ -93,10 +99,7 @@ export function ActivityThreadOptionsMenu({
variant="ghost"
size="icon-xs"
className="relative text-muted-foreground"
aria-label={translate(
'auto.components.activity.ActivityPrototypePage.db8a1878b5',
'Thread list options'
)}
aria-label={optionsLabel}
>
<ListFilter className="size-3.5" strokeWidth={2.25} />
{scopeFilterActive ? (
@@ -29,7 +29,7 @@ function ActivityThreadRowAction({
type="button"
variant="ghost"
size="icon-xs"
className="size-4 p-0 text-muted-foreground opacity-100 transition-opacity hover:text-foreground can-hover:opacity-0 group-hover:opacity-100 focus-visible:opacity-100"
className="size-4 p-0 text-muted-foreground transition-opacity hover:text-foreground can-hover:pointer-events-none can-hover:opacity-0 can-hover:group-hover:pointer-events-auto can-hover:group-hover:opacity-100 focus-visible:opacity-100"
aria-label={label}
onClick={(event) => {
event.stopPropagation()
+2 -1
View File
@@ -16161,7 +16161,8 @@
"search": "Search",
"showUnreadOnly": "Show unread only",
"showChildAgents": "Show child agents",
"activityOptions": "Activity options"
"activityOptions": "Activity options",
"threadListOptionsFiltered": "Thread list options, filters active"
},
"clearCompleted": {
"clearedOne": "Cleared 1 completed agent",
+4
View File
@@ -14180,6 +14180,7 @@
"770d458144": "Agrupar por",
"795cbf26e2": "Filtrar...",
"4616ea39fd": "Ir al workspace",
"threadListOptionsFiltered": "Opciones de lista de hilos, filtros activos",
"markThreadRead": "Marcar hilo como leído",
"59b131fbd9": "Marcar hilo como no leído",
"beb2c19173": "No leído",
@@ -14187,6 +14188,9 @@
"22b22034bc": "Terminal independiente no disponible en Actividad.",
"afdc2139a8": "Terminal de Agent cerrada. Abre una nueva terminal en este workspace para continuar."
},
"ActivityThreadRow": {
"clearNotification": "Borrar notificación"
},
"ActivityTitlebarControls": {
"f915168c8e": "no leído",
"d6a8de3934": "agentes",
+4
View File
@@ -15462,12 +15462,16 @@
"770d458144": "Grouper l'activité des agents par",
"795cbf26e2": "Filtrer...",
"4616ea39fd": "Aller à l'espace de travail",
"threadListOptionsFiltered": "Options de la liste des fils, filtres actifs",
"59b131fbd9": "Marquer le fil comme non lu",
"beb2c19173": "Non lus",
"5651b216c6": "Projet inconnu",
"22b22034bc": "Terminal autonome indisponible dans Activité.",
"afdc2139a8": "Terminal de l'agent fermé. Ouvrez un nouveau terminal dans cet espace de travail pour continuer."
},
"ActivityThreadRow": {
"clearNotification": "Effacer la notification"
},
"ActivityTitlebarControls": {
"f915168c8e": "non lu",
"d6a8de3934": "agents",
+4
View File
@@ -14180,6 +14180,7 @@
"770d458144": "グループ化",
"795cbf26e2": "フィルター…",
"4616ea39fd": "ワークスペースにジャンプ",
"threadListOptionsFiltered": "スレッドリストのオプション、フィルター適用中",
"markThreadRead": "スレッドを既読としてマーク",
"59b131fbd9": "スレッドを未読としてマークする",
"beb2c19173": "未読",
@@ -14187,6 +14188,9 @@
"22b22034bc": "スタンドアロンターミナルはアクティビティでは使用できません。",
"afdc2139a8": "Agent ターミナルが閉じられました。続行するには、このワークスペースで新規ターミナルを開いてください。"
},
"ActivityThreadRow": {
"clearNotification": "通知を消去"
},
"ActivityTitlebarControls": {
"f915168c8e": "未読",
"d6a8de3934": "Agent",
+4
View File
@@ -14258,6 +14258,7 @@
"770d458144": "그룹화 기준",
"795cbf26e2": "필터...",
"4616ea39fd": "워크스페이스로 이동",
"threadListOptionsFiltered": "스레드 목록 옵션, 필터 활성화됨",
"markThreadRead": "스레드를 읽은 것으로 표시",
"59b131fbd9": "스레드를 읽지 않은 것으로 표시",
"beb2c19173": "읽지 않음",
@@ -14265,6 +14266,9 @@
"22b22034bc": "활동에서는 독립형 terminal을 사용할 수 없습니다.",
"afdc2139a8": "Agent terminal이 닫혔습니다. 계속하려면 이 워크스페이스에서 새 terminal을 여세요."
},
"ActivityThreadRow": {
"clearNotification": "알림 지우기"
},
"ActivityTitlebarControls": {
"f915168c8e": "읽지 않음",
"d6a8de3934": "에이전트",
+4
View File
@@ -14236,6 +14236,7 @@
"770d458144": "分组方式",
"795cbf26e2": "筛选...",
"4616ea39fd": "跳转到工作区",
"threadListOptionsFiltered": "线程列表选项,筛选器已启用",
"markThreadRead": "将话题标记为已读",
"59b131fbd9": "将话题标记为未读",
"beb2c19173": "未读",
@@ -14243,6 +14244,9 @@
"22b22034bc": "独立终端在活动中不可用。",
"afdc2139a8": "智能体终端关闭。在此工作区中打开一个新终端以继续。"
},
"ActivityThreadRow": {
"clearNotification": "清除通知"
},
"ActivityTitlebarControls": {
"f915168c8e": "未读",
"d6a8de3934": "智能体",