feat: redesign run history views

This commit is contained in:
mesalogo
2026-08-15 18:06:03 +08:00
parent 9cd565fcc4
commit 67d30d9e21
9 changed files with 1392 additions and 394 deletions
+159 -53
View File
@@ -81,7 +81,7 @@ describe('ActivityPanel', () => {
await i18n.changeLanguage('zh-CN')
})
it('renders English interface copy while preserving activity content', async () => {
it('renders localized tabs and keeps usage separate from activity', async () => {
await i18n.changeLanguage('en-US')
const record = makeRecord(1, 'running')
render(
@@ -102,19 +102,67 @@ describe('ActivityPanel', () => {
}).format(new Date(record.createdAt))
expect(screen.getAllByText(englishDate).length).toBeGreaterThan(0)
expect(
screen.getByRole('heading', {
level: 1,
name: 'Tasks and activity'
})
screen.getByRole('heading', { level: 1, name: 'Run history' })
).toBeInTheDocument()
expect(
screen.getByRole('button', { name: 'In progress' })
).toBeInTheDocument()
screen.getByRole('tab', { name: 'Tasks and conversations' })
).toHaveAttribute('aria-selected', 'true')
expect(screen.getByText(record.title)).toBeInTheDocument()
expect(screen.queryByText('Token usage')).not.toBeInTheDocument()
fireEvent.click(
screen.getByRole('tab', { name: 'Usage analytics' })
)
expect(screen.getByText('Token usage')).toBeInTheDocument()
expect(screen.queryByText(record.title)).not.toBeInTheDocument()
})
it('filters active and unsuccessful activity and opens its conversation', () => {
it('organizes records by project, conversation, and detail', () => {
const request: ActivityRecord = {
...makeRecord(1, 'running'),
kind: 'request',
title: '分析登录问题',
scope: {
kind: 'project',
projectId: 'project-1',
projectName: '项目甲'
}
}
const tool: ActivityRecord = {
...makeRecord(2),
conversationId: request.conversationId,
requestId: request.requestId,
title: '搜索 IPC 代码',
scope: request.scope
}
const { container } = render(
<ActivityPanel
onClear={vi.fn()}
onOpenConversation={vi.fn()}
records={[request, tool, makeRecord(3)]}
tokenUsage={makeTokenUsage()}
/>
)
expect(container.querySelectorAll('.activity-project')).toHaveLength(2)
expect(screen.getByText('项目:项目甲')).toBeInTheDocument()
expect(
screen.getByText('1 个任务或会话 · 2 条活动')
).toBeInTheDocument()
const projectConversation = screen
.getByText('对话:分析登录问题')
.closest('details')
expect(projectConversation).not.toBeNull()
expect(projectConversation).not.toHaveAttribute('open')
fireEvent.click(screen.getByText('对话:分析登录问题'))
expect(projectConversation).toHaveAttribute('open')
expect(
projectConversation?.querySelectorAll('article')
).toHaveLength(2)
})
it('filters active and exceptional activity and opens its conversation', () => {
const onOpenConversation = vi.fn()
render(
<ActivityPanel
@@ -131,16 +179,15 @@ describe('ActivityPanel', () => {
)
fireEvent.click(screen.getByRole('button', { name: '进行中' }))
fireEvent.click(screen.getByText('对话:活动 1'))
expect(screen.getByText('活动 1')).toBeInTheDocument()
expect(screen.getByText('对话:活动 1')).toBeInTheDocument()
expect(screen.queryByText('活动 2')).not.toBeInTheDocument()
fireEvent.click(screen.getByRole('button', { name: '失败' }))
fireEvent.click(screen.getByText('对话:活动 2'))
expect(screen.getByText('活动 2')).toBeInTheDocument()
expect(screen.getByText('活动 3')).toBeInTheDocument()
fireEvent.click(screen.getByRole('button', { name: '异常' }))
expect(screen.getByText('对话:活动 2')).toBeInTheDocument()
expect(screen.getByText('对话:活动 3')).toBeInTheDocument()
expect(screen.queryByText('活动 1')).not.toBeInTheDocument()
fireEvent.click(screen.getByText('对话:活动 2'))
fireEvent.click(
screen.getAllByRole('button', { name: '打开所属对话' })[0]!
)
@@ -236,35 +283,7 @@ describe('ActivityPanel', () => {
expect(within(item).getByText('进行中')).toBeInTheDocument()
})
it('groups activity by conversation in collapsible sections', () => {
const first = makeRecord(1, 'running')
const second = {
...makeRecord(2, 'failed'),
conversationId: first.conversationId
}
const { container } = render(
<ActivityPanel
onClear={vi.fn()}
onOpenConversation={vi.fn()}
records={[first, second]}
tokenUsage={makeTokenUsage()}
/>
)
const groups =
container.querySelectorAll<HTMLDetailsElement>(
'details.activity-group'
)
expect(groups).toHaveLength(1)
expect(groups[0]).not.toHaveAttribute('open')
expect(within(groups[0]!).getByText('2 条活动')).toBeInTheDocument()
fireEvent.click(within(groups[0]!).getByText('对话:活动 1'))
expect(groups[0]).toHaveAttribute('open')
expect(groups[0]!.querySelectorAll('article')).toHaveLength(2)
})
it('shows immutable scope snapshots on groups and records', () => {
it('shows immutable project and unavailable scope snapshots', () => {
const projectRecord: ActivityRecord = {
...makeRecord(1),
scope: {
@@ -286,11 +305,11 @@ describe('ActivityPanel', () => {
/>
)
expect(screen.getAllByText('项目:项目甲')).toHaveLength(2)
expect(screen.getAllByText('范围不可用')).toHaveLength(2)
expect(screen.getByText('项目:项目甲')).toBeInTheDocument()
expect(screen.getByText('范围不可用')).toBeInTheDocument()
})
it('uses the shared page hierarchy and explicit global scope', () => {
it('uses shared page tabs and explicit global scope', () => {
render(
<ActivityPanel
onClear={vi.fn()}
@@ -301,15 +320,102 @@ describe('ActivityPanel', () => {
)
expect(
screen.getByRole('heading', { level: 1, name: '任务与活动' })
screen.getByRole('heading', { level: 1, name: '运行记录' })
).toBeInTheDocument()
expect(screen.getByText('全部项目')).toHaveClass('scope-badge')
expect(screen.getByLabelText('Token 用量分组')).toHaveClass(
'segmented-control'
expect(screen.getByLabelText('运行记录视图')).toHaveClass(
'page-tabs'
)
expect(
screen.getByRole('tabpanel', { name: '任务与会话' })
).toBeInTheDocument()
expect(screen.getByLabelText('筛选活动')).toHaveClass(
'segmented-control'
)
expect(
screen.queryByLabelText('Token 用量分组')
).not.toBeInTheDocument()
})
it('renders shared-time project and conversation activity tracks', () => {
const first: ActivityRecord = {
...makeRecord(1, 'running'),
kind: 'request',
title: '分析登录问题',
scope: {
kind: 'project',
projectId: 'project-1',
projectName: '项目甲'
}
}
const second: ActivityRecord = {
...makeRecord(2, 'failed'),
conversationId: first.conversationId,
requestId: first.requestId,
scope: first.scope
}
const third: ActivityRecord = {
...makeRecord(3),
conversationId: 'conversation-parallel',
requestId: 'request-parallel',
kind: 'subagent',
title: '研究专家',
scope: first.scope
}
const fourth: ActivityRecord = {
...makeRecord(4),
conversationId: first.conversationId,
requestId: first.requestId,
kind: 'result',
title: '任务执行完成',
scope: first.scope
}
render(
<ActivityPanel
onClear={vi.fn()}
onOpenConversation={vi.fn()}
records={[first, second, third, fourth]}
tokenUsage={makeTokenUsage()}
/>
)
fireEvent.click(screen.getByRole('tab', { name: '活动时间线' }))
const timeline = screen.getByLabelText(
'按项目和会话分组的并行活动轨道'
)
expect(timeline).toHaveClass('activity-tracks')
expect(
timeline.querySelectorAll('.activity-track')
).toHaveLength(2)
expect(
timeline.querySelectorAll('.activity-track__node')
).toHaveLength(4)
expect(within(timeline).getByText('分析登录问题')).toBeInTheDocument()
expect(
within(timeline).getAllByText('研究专家').length
).toBeGreaterThan(0)
expect(within(timeline).getAllByText('用户').length).toBeGreaterThan(0)
expect(
within(timeline).getAllByText('GoodBuddy').length
).toBeGreaterThan(0)
expect(within(timeline).getByText('项目:项目甲')).toBeInTheDocument()
expect(
within(timeline).getByText(
'所有轨道共享同一执行顺序,节点按发生时间依次展开,点击节点查看身份和活动详情。'
)
).toBeInTheDocument()
fireEvent.click(
screen.getByRole('button', {
name: / 2/u
})
)
const detail = screen.getByLabelText('选中的活动节点详情')
expect(within(detail).getByText('活动 2')).toBeInTheDocument()
expect(
within(detail).getByText('对话:分析登录问题')
).toBeInTheDocument()
expect(within(detail).getByText('项目:项目甲')).toBeInTheDocument()
})
it('never renders more than 500 records', () => {
@@ -339,11 +445,10 @@ describe('ActivityPanel', () => {
tokenUsage={makeTokenUsage()}
/>
)
fireEvent.click(screen.getByRole('tab', { name: '用量统计' }))
const stats = screen.getByLabelText('Token 用量统计')
expect(
within(stats).getByText('150')
).toBeInTheDocument()
expect(within(stats).getByText('150')).toBeInTheDocument()
const projectRow = screen.getByRole('row', {
name: '项目甲gpt-5 · openai 100 20 10 40 120'
@@ -354,7 +459,7 @@ describe('ActivityPanel', () => {
).not.toBeInTheDocument()
})
it('groups token usage and displays fallback labels', () => {
it('groups token usage by project, conversation, and model', () => {
render(
<ActivityPanel
onClear={vi.fn()}
@@ -363,6 +468,7 @@ describe('ActivityPanel', () => {
tokenUsage={makeTokenUsage()}
/>
)
fireEvent.click(screen.getByRole('tab', { name: '用量统计' }))
expect(screen.getByText('项目甲')).toBeInTheDocument()
expect(screen.getByText('未归属项目')).toBeInTheDocument()
File diff suppressed because it is too large Load Diff
+7 -4
View File
@@ -3067,10 +3067,13 @@ describe('App', () => {
throw new Error('Missing request')
}
fireEvent.click(screen.getByText('任务与活动'))
fireEvent.click(screen.getByText('运行记录'))
fireEvent.click(
await screen.findByRole('tab', { name: '用量统计' })
)
const stats = await screen.findByLabelText('Token 用量统计')
expect(
screen.getByRole('heading', { level: 1, name: '任务与活动' })
screen.getByRole('heading', { level: 1, name: '运行记录' })
).toBeInTheDocument()
expect(
screen.queryByRole('button', { name: /^/u })
@@ -3841,7 +3844,7 @@ describe('App', () => {
)
expect(cancelledDot).toHaveClass('message__status-dot')
expect(cancelledDot).not.toHaveClass('message__status-dot--active')
fireEvent.click(screen.getByText('任务与活动'))
fireEvent.click(screen.getByText('运行记录'))
expect((await screen.findAllByText('已取消')).length).toBeGreaterThan(0)
fireEvent.click(screen.getByRole('button', { name: '进行中' }))
expect(
@@ -5170,7 +5173,7 @@ describe('App', () => {
expect(within(statusRegion).getByText('已取消')).toBeInTheDocument()
expect(within(statusRegion).getByText('父任务已停止')).toBeInTheDocument()
fireEvent.click(screen.getByText('任务与活动'))
fireEvent.click(screen.getByText('运行记录'))
expect(await screen.findAllByText('子专家')).toHaveLength(4)
expect(screen.getAllByText(//u).length).toBeGreaterThan(0)
expect(screen.getAllByText(//u).length).toBeGreaterThan(0)
@@ -3,10 +3,16 @@ import type { activity as chineseActivity } from '../zh-CN/activity'
export const activity = {
header: {
eyebrow: 'ACTIVITY AUDIT',
title: 'Tasks and activity',
eyebrow: 'RUN HISTORY',
title: 'Run history',
description:
'View task requests, child experts, tool calls, approval results, and token usage across all projects.'
'Review execution details by project, task, and conversation, or browse the activity timeline and model usage.'
},
tabs: {
ariaLabel: 'Run history views',
tasks: 'Tasks and conversations',
timeline: 'Activity timeline',
usage: 'Usage analytics'
},
clear: {
confirmAriaLabel: 'Confirm clearing activity history',
@@ -34,7 +40,7 @@ export const activity = {
filters: {
all: 'All',
active: 'In progress',
failed: 'Failed',
failed: 'Exceptions',
ariaLabel: 'Filter activity',
clear: 'Clear filter'
},
@@ -69,7 +75,32 @@ export const activity = {
ariaLabel: 'Activity totals',
all: 'All',
active: 'In progress',
failed: 'Failed'
failed: 'Exceptions'
},
timeline: {
ariaLabel: 'Parallel activity tracks grouped by project and conversation',
description:
'Every track shares the same execution order. Nodes are spread by event time. Select a node to inspect its identity and details.',
lanes: 'Project / conversation',
laneAriaLabel: 'Activity track for conversation {{title}}',
nodeAriaLabel: '{{actor}}, {{kind}}, {{status}}, {{title}}, {{time}}',
legendAriaLabel: 'Activity node identity legend',
detailAriaLabel: 'Selected activity node details',
closeDetail: 'Close details',
actors: {
user: 'User',
assistant: 'GoodBuddy',
subagent: 'Child expert',
tool: 'Tool',
approval: 'Approval'
},
nodes: {
user: 'U',
assistant: 'G',
tool: 'T',
approval: 'A',
subagent: 'S'
}
},
empty: {
active: 'There is no pending or running activity.',
@@ -87,6 +118,8 @@ export const activity = {
'The scope could not be determined when this activity was recorded.',
conversation: 'Conversation: {{title}}',
activityCount: 'Activity items: {{formattedCount}}',
projectSummary:
'{{conversationCount}} tasks or conversations · {{activityCount}} activity items',
openConversation: 'Open conversation'
}
} satisfies TranslationShape<typeof chineseActivity>
+1 -1
View File
@@ -46,7 +46,7 @@ export const app = {
magicNotes: 'Magic Notes',
knowledge: 'Knowledge',
heartbeat: 'Smart Heartbeat',
activity: 'Tasks & Activity',
activity: 'Run history',
pendingSuggestions: '{{count}} pending suggestions'
},
route: {
@@ -1,9 +1,15 @@
export const activity = {
header: {
eyebrow: 'ACTIVITY AUDIT',
title: '任务与活动',
eyebrow: 'RUN HISTORY',
title: '运行记录',
description:
'查看全部项目中的任务请求、子专家、工具调用、审批结果和 Token 用量。'
'按项目、任务与会话查看执行详情,或浏览活动时间线和模型用量。'
},
tabs: {
ariaLabel: '运行记录视图',
tasks: '任务与会话',
timeline: '活动时间线',
usage: '用量统计'
},
clear: {
confirmAriaLabel: '确认清空 {{formattedCount}} 条活动记录',
@@ -31,7 +37,7 @@ export const activity = {
filters: {
all: '全部',
active: '进行中',
failed: '失败',
failed: '异常',
ariaLabel: '筛选活动',
clear: '清除筛选'
},
@@ -66,7 +72,32 @@ export const activity = {
ariaLabel: '活动统计',
all: '全部',
active: '进行中',
failed: '失败'
failed: '异常'
},
timeline: {
ariaLabel: '按项目和会话分组的并行活动轨道',
description:
'所有轨道共享同一执行顺序,节点按发生时间依次展开,点击节点查看身份和活动详情。',
lanes: '项目 / 会话',
laneAriaLabel: '会话 {{title}} 的活动轨道',
nodeAriaLabel: '{{actor}}{{kind}}{{status}}{{title}}{{time}}',
legendAriaLabel: '活动节点身份图例',
detailAriaLabel: '选中的活动节点详情',
closeDetail: '关闭详情',
actors: {
user: '用户',
assistant: 'GoodBuddy',
subagent: '子专家',
tool: '工具',
approval: '审批'
},
nodes: {
user: '用',
assistant: 'G',
tool: '工',
approval: '审',
subagent: '专'
}
},
empty: {
active: '当前没有等待中或正在运行的活动。',
@@ -83,6 +114,8 @@ export const activity = {
'创建此活动记录时未能确定其归属范围。',
conversation: '对话:{{title}}',
activityCount: '{{formattedCount}} 条活动',
projectSummary:
'{{conversationCount}} 个任务或会话 · {{activityCount}} 条活动',
openConversation: '打开所属对话'
}
} as const
+1 -1
View File
@@ -42,7 +42,7 @@ export const app = {
magicNotes: '魔法笔记',
knowledge: '知识库',
heartbeat: '智能心跳',
activity: '任务与活动',
activity: '运行记录',
pendingSuggestions: '{{count}} 条待处理建议'
},
route: {
+404 -11
View File
@@ -8445,6 +8445,16 @@ details.settings-section > :not(summary) + :not(summary) {
width: 100%;
min-height: 100%;
flex-direction: column;
container: activity-panel / inline-size;
}
.activity-panel > .page-tabs {
padding: var(--space-4) 0;
}
.activity-panel__tab-panel {
min-width: 0;
padding-bottom: var(--space-6);
}
.activity-list {
@@ -8458,7 +8468,6 @@ details.settings-section > :not(summary) + :not(summary) {
padding: 14px;
border: 1px solid var(--border-default);
border-radius: var(--radius-card);
margin-top: var(--space-4);
background: var(--surface-raised);
}
@@ -8468,11 +8477,11 @@ details.settings-section > :not(summary) + :not(summary) {
gap: 12px;
}
.token-usage__header h3 {
.token-usage__header h2 {
flex: 1;
margin: 0;
color: var(--text-primary);
font-size: 13px;
font-size: var(--font-section-title);
}
.token-usage__stats {
@@ -8561,7 +8570,7 @@ details.settings-section > :not(summary) + :not(summary) {
.activity-panel__stats {
display: grid;
margin: 18px 0 14px;
margin: 0 0 var(--space-3);
gap: 9px;
grid-template-columns: repeat(3, 1fr);
}
@@ -8585,22 +8594,54 @@ details.settings-section > :not(summary) + :not(summary) {
font-weight: 700;
}
.activity-panel__filters {
.activity-panel__toolbar {
display: flex;
flex-wrap: wrap;
margin-bottom: 12px;
gap: 6px;
align-items: flex-start;
justify-content: space-between;
margin-bottom: var(--space-3);
gap: var(--space-3);
}
.activity-groups {
.activity-projects {
display: grid;
gap: var(--space-4);
}
.activity-project {
overflow: clip;
border: 1px solid var(--border-default);
border-radius: var(--radius-card);
background: var(--surface-raised);
}
.activity-project__header {
display: flex;
min-height: 44px;
align-items: center;
justify-content: space-between;
padding: var(--space-2) var(--space-4);
border-bottom: 1px solid var(--border-subtle);
background: var(--surface-subtle);
gap: var(--space-3);
}
.activity-project__header > span:last-child {
color: var(--text-muted);
font-size: var(--font-caption);
}
.activity-conversations {
display: grid;
padding: var(--space-3);
background: var(--surface-canvas);
gap: var(--space-2);
}
.activity-group {
overflow: clip;
border: 1px solid var(--border-default);
border-radius: var(--radius-card);
border-radius: var(--radius-control);
background: var(--surface-raised);
}
@@ -8648,11 +8689,15 @@ details.settings-section > :not(summary) + :not(summary) {
.activity-group > .activity-list {
display: grid;
padding: var(--space-3);
background: var(--surface-canvas);
border-top: 1px solid var(--border-subtle);
gap: var(--space-2);
}
.activity-group .activity-item {
.activity-list__item {
list-style: none;
}
.activity-list__item .activity-item {
margin-bottom: 0;
}
@@ -8725,6 +8770,23 @@ details.settings-section > :not(summary) + :not(summary) {
white-space: pre-wrap;
}
.activity-item__context {
display: flex;
min-width: 0;
flex-wrap: wrap;
align-items: center;
margin-top: var(--space-2);
color: var(--text-muted);
font-size: var(--font-caption);
gap: var(--space-2);
}
.activity-item__context > span:first-child {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.activity-item__conversation {
padding: 5px 0;
margin-top: 7px;
@@ -8735,6 +8797,305 @@ details.settings-section > :not(summary) + :not(summary) {
font-weight: 650;
}
.activity-tracks {
display: grid;
min-width: 0;
gap: var(--space-3);
}
.activity-tracks__description {
padding: var(--space-2) var(--space-3);
border-radius: var(--radius-control);
margin: 0;
background: var(--surface-subtle);
color: var(--text-secondary);
font-size: var(--font-caption);
line-height: 1.5;
}
.activity-tracks__legend {
display: flex;
padding: 0;
margin: 0;
color: var(--text-secondary);
flex-wrap: wrap;
font-size: var(--font-caption);
gap: var(--space-3);
list-style: none;
}
.activity-tracks__legend li {
display: inline-flex;
align-items: center;
gap: var(--space-1);
}
.activity-tracks__legend-swatch {
width: 10px;
height: 10px;
border-radius: 50%;
background: var(--accent-solid);
}
.activity-tracks__legend-swatch--assistant {
background: var(--success);
}
.activity-tracks__legend-swatch--subagent {
background: var(--warning);
}
.activity-tracks__legend-swatch--tool {
background: color-mix(
in srgb,
var(--accent) 55%,
var(--success)
);
}
.activity-tracks__legend-swatch--approval {
border-radius: 3px;
background: color-mix(
in srgb,
var(--warning) 65%,
var(--danger)
);
}
.activity-tracks__scroll {
max-width: 100%;
border: 1px solid var(--border-default);
border-radius: var(--radius-card);
overflow-x: auto;
background: var(--surface-raised);
}
.activity-tracks__canvas {
width: 100%;
min-width: 760px;
}
.activity-tracks__axis {
display: grid;
min-height: 38px;
align-items: center;
padding: 0 var(--space-3);
border-bottom: 1px solid var(--border-subtle);
color: var(--text-muted);
font-size: var(--font-caption);
grid-template-columns: 210px minmax(0, 1fr) auto;
}
.activity-tracks__axis strong {
position: sticky;
z-index: 3;
left: var(--space-3);
padding-right: var(--space-3);
background: var(--surface-raised);
color: var(--text-secondary);
}
.activity-tracks__axis span:nth-child(2) {
padding-left: var(--space-3);
}
.activity-tracks__project > header {
display: flex;
min-height: 38px;
align-items: center;
justify-content: space-between;
padding: var(--space-2) var(--space-3);
border-bottom: 1px solid var(--border-subtle);
background: var(--surface-subtle);
gap: var(--space-3);
}
.activity-tracks__project > header > span:last-child {
color: var(--text-muted);
font-size: var(--font-caption);
}
.activity-track {
display: grid;
min-height: 84px;
border-bottom: 1px solid var(--border-subtle);
grid-template-columns: 210px minmax(0, 1fr);
}
.activity-tracks__project:last-child .activity-track:last-child {
border-bottom: 0;
}
.activity-track__label {
position: sticky;
z-index: 2;
left: 0;
display: grid;
min-width: 0;
align-content: center;
padding: var(--space-2) var(--space-3);
border-right: 1px solid var(--border-subtle);
background: var(--surface-raised);
gap: var(--space-1);
}
.activity-track__label > strong {
overflow: hidden;
color: var(--text-primary);
font-size: var(--font-body);
text-overflow: ellipsis;
white-space: nowrap;
}
.activity-track__label > span {
display: flex;
align-items: center;
color: var(--text-muted);
font-size: var(--font-caption);
gap: var(--space-2);
}
.activity-track__rail {
position: relative;
min-width: 0;
background-image: linear-gradient(
to right,
var(--border-subtle) 1px,
transparent 1px
);
background-size: 20% 100%;
}
.activity-track__rail::before {
position: absolute;
top: 50%;
right: var(--space-3);
left: var(--space-3);
height: 2px;
background: var(--border-default);
content: '';
transform: translateY(-50%);
}
.activity-track__node {
position: absolute;
z-index: 1;
top: 50%;
display: flex;
width: 68px;
height: 58px;
padding: 0;
border: 0;
background: transparent;
color: var(--text-secondary);
cursor: pointer;
align-items: center;
flex-direction: column;
font-size: 10px;
gap: 2px;
transform: translate(-50%, -50%);
}
.activity-track__node:hover {
z-index: 3;
color: var(--text-primary);
transform: translate(-50%, -50%) scale(1.05);
}
.activity-track__node:focus-visible {
z-index: 4;
outline: 2px solid var(--accent);
outline-offset: 2px;
}
.activity-track__node > span {
display: grid;
width: 32px;
height: 32px;
border: 2px solid var(--surface-raised);
border-radius: 50%;
background: var(--accent-solid);
color: var(--text-on-accent);
font-weight: 750;
place-items: center;
}
.activity-track__node > small {
display: block;
width: 100%;
overflow: hidden;
font-size: 9px;
line-height: 1.2;
text-align: center;
text-overflow: ellipsis;
white-space: nowrap;
}
.activity-track__node[aria-pressed='true'] > span {
box-shadow: 0 0 0 3px var(--accent-selected);
}
.activity-track__node--actor-assistant > span {
background: var(--success);
}
.activity-track__node--actor-subagent > span {
background: var(--warning);
color: var(--text-primary);
}
.activity-track__node--actor-tool > span {
background: color-mix(
in srgb,
var(--accent) 55%,
var(--success)
);
}
.activity-track__node--actor-approval > span {
border-radius: 6px;
background: color-mix(
in srgb,
var(--warning) 65%,
var(--danger)
);
}
.activity-track__node--running > span {
box-shadow: 0 0 0 3px var(--accent-selected);
}
.activity-track__node--failed > span,
.activity-track__node--denied > span,
.activity-track__node--cancelled > span,
.activity-track__node--interrupted > span {
border-color: var(--danger);
box-shadow: 0 0 0 2px var(--danger-subtle);
}
.activity-track__node--pending > span {
border-color: var(--accent);
background: var(--surface-raised);
color: var(--accent);
}
.activity-tracks__detail {
display: grid;
padding: var(--space-3);
border: 1px solid var(--border-default);
border-radius: var(--radius-card);
background: var(--surface-subtle);
gap: var(--space-2);
}
.activity-tracks__detail > .secondary-button {
justify-self: end;
}
.activity-tracks__detail .activity-item {
margin: 0;
}
.heartbeat-center {
display: flex;
width: 100%;
@@ -9820,6 +10181,9 @@ details.settings-section > :not(summary) + :not(summary) {
grid-template-columns: 1fr;
}
}
@container activity-panel (max-width: 720px) {
.activity-group > summary {
grid-template-columns: minmax(0, 1fr) auto;
}
@@ -9827,6 +10191,35 @@ details.settings-section > :not(summary) + :not(summary) {
.activity-group > summary time {
grid-column: 1 / -1;
}
.activity-panel__toolbar {
align-items: stretch;
flex-direction: column;
}
.activity-panel__toolbar > .segmented-control {
width: fit-content;
}
}
@container activity-panel (max-width: 480px) {
.activity-panel__stats {
grid-template-columns: 1fr;
}
.activity-project__header {
align-items: flex-start;
flex-direction: column;
}
.activity-conversations {
padding: var(--space-2);
}
.activity-item__context > span:first-child {
width: 100%;
white-space: normal;
}
}
@container heartbeat-center (max-width: 620px) {