diff --git a/apps/web/tests/workflow-run.e2e.ts b/apps/web/tests/workflow-run.e2e.ts
index cc20bdcc6f..4cbae8e6e2 100644
--- a/apps/web/tests/workflow-run.e2e.ts
+++ b/apps/web/tests/workflow-run.e2e.ts
@@ -82,6 +82,8 @@ describe.skipIf(MODE === 'record')('web e2e: durable workflow run in Chat', () =
expect(await disclosures.nth(0).getAttribute('aria-expanded')).toBeNull()
expect(await disclosures.nth(1).getAttribute('role')).toBeNull()
expect(await disclosures.nth(1).getAttribute('aria-expanded')).toBeNull()
+ expect(await disclosures.nth(0).evaluate(element => getComputedStyle(element).cursor)).not.toBe('pointer')
+ expect(await disclosures.nth(1).evaluate(element => getComputedStyle(element).cursor)).not.toBe('pointer')
const member = page.getByRole('button', { name: /^Open Reply with exactly the word/ })
await member.waitFor({ timeout: 15_000 })
await member.focus()
@@ -148,10 +150,12 @@ describe.skipIf(MODE === 'record')('web e2e: durable workflow run in Chat', () =
const terminalWorkflow = page.getByRole('button', { name: /^snapshot-flow/ })
await terminalWorkflow.waitFor()
expect(await terminalWorkflow.getAttribute('aria-expanded')).toBe('false')
+ expect(await terminalWorkflow.evaluate(element => getComputedStyle(element).cursor)).toBe('pointer')
await terminalWorkflow.click()
const terminalPhase = page.getByRole('button', { name: /^Run/ })
await terminalPhase.waitFor()
expect(await terminalPhase.getAttribute('aria-expanded')).toBe('false')
+ expect(await terminalPhase.evaluate(element => getComputedStyle(element).cursor)).toBe('pointer')
await terminalPhase.click()
await page.getByText(CHILD_PROMPT, { exact: false }).waitFor()
await expect.poll(
diff --git a/packages/client/ui-workflow-run/src/client/WorkflowRunPanel.module.css b/packages/client/ui-workflow-run/src/client/WorkflowRunPanel.module.css
index 77145ee06a..fdb54fdde2 100644
--- a/packages/client/ui-workflow-run/src/client/WorkflowRunPanel.module.css
+++ b/packages/client/ui-workflow-run/src/client/WorkflowRunPanel.module.css
@@ -14,7 +14,6 @@
padding: 0 8px;
border-radius: 8px;
background: var(--dsw-alias-bg-module-platform);
- cursor: pointer;
}
.runHeader:focus-visible {
@@ -78,7 +77,6 @@
width: 100%;
min-width: 0;
height: 32px;
- cursor: pointer;
}
.phaseHeader:focus-visible {
diff --git a/packages/client/ui-workflow-run/src/client/WorkflowRunPanel.tsx b/packages/client/ui-workflow-run/src/client/WorkflowRunPanel.tsx
index c200f99bfd..59883e5ae6 100644
--- a/packages/client/ui-workflow-run/src/client/WorkflowRunPanel.tsx
+++ b/packages/client/ui-workflow-run/src/client/WorkflowRunPanel.tsx
@@ -84,10 +84,11 @@ function ManualDisclosure(props: StatusDisclosureProps) {
)
}
-function StatusDisclosure({ requiresExpansion, ...props }: StatusDisclosureProps & {
+function StatusDisclosure({ cleanCycleKey, requiresExpansion, ...props }: StatusDisclosureProps & {
+ readonly cleanCycleKey: number
readonly requiresExpansion: boolean
}) {
- if (!requiresExpansion) return
+ if (!requiresExpansion) return
return
}
@@ -138,6 +139,7 @@ function RunHeader({ children, count, name, requiresExpansion, status, t }: {
}
title={t('run.title', { name })}
+ cleanCycleKey={count}
requiresExpansion={requiresExpansion}
expandOnRowClick
previewChevron={false}
@@ -201,6 +203,7 @@ function PhaseSection({ phase, navigable, openSession, t }: {
}
title={readablePhase(phase.phase, t)}
+ cleanCycleKey={phase.members.length}
requiresExpansion={phaseRequiresExpansion(phase)}
expandOnRowClick
previewChevron={false}
diff --git a/packages/client/ui-workflow-run/tests/workflow-run.spec.tsx b/packages/client/ui-workflow-run/tests/workflow-run.spec.tsx
index 3d8d1c798f..55ee32b8d1 100644
--- a/packages/client/ui-workflow-run/tests/workflow-run.spec.tsx
+++ b/packages/client/ui-workflow-run/tests/workflow-run.spec.tsx
@@ -377,6 +377,45 @@ describe('WorkflowRunPanel', () => {
expect(screen.queryByText('未分阶段')).toBeNull()
})
+ it('refolds when a complete activity cycle arrives as one clean update', () => {
+ const firstMember = {
+ seq: 1, label: 'first', childId: 'child-1' as SessionId, status: 'completed' as const,
+ }
+ const phaseClean: WorkflowRunChatData = {
+ name: 'phase-cycle', status: 'running',
+ phases: [phase({ members: [firstMember] })],
+ }
+ const phaseView = render()
+ fireEvent.click(screen.getByRole('button', { name: /未分阶段/ }))
+ expect(screen.getByText('first')).toBeTruthy()
+ phaseView.rerender()
+ expect(screen.getByRole('button', { name: /未分阶段/ }).getAttribute('aria-expanded')).toBe('false')
+ expect(screen.queryByText('first')).toBeNull()
+ expect(screen.queryByText('second')).toBeNull()
+ phaseView.unmount()
+
+ const workflowClean: WorkflowRunChatData = {
+ name: 'workflow-cycle', status: 'completed',
+ phases: [phase({ members: [firstMember] })],
+ }
+ const workflowView = render()
+ fireEvent.click(screen.getByRole('button', { name: /^workflow-cycle/ }))
+ expect(screen.getByText('未分阶段')).toBeTruthy()
+ workflowView.rerender()
+ expect(screen.getByRole('button', { name: /^workflow-cycle/ }).getAttribute('aria-expanded')).toBe('false')
+ expect(screen.queryByText('未分阶段')).toBeNull()
+ })
+
it('derives the zero-member running and completed states from the current run status', () => {
const running: WorkflowRunChatData = { name: 'empty', status: 'running', phases: [] }
const view = render()