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()