Skip to content

Commit 6331d1e

Browse files
committed
fix(logs): preserve snapshot modal while loading
1 parent 357d6a2 commit 6331d1e

4 files changed

Lines changed: 98 additions & 7 deletions

File tree

apps/sim/app/workspace/[workspaceId]/logs/components/log-details/components/execution-snapshot/snapshot-boundary.test.tsx

Lines changed: 43 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
/**
22
* @vitest-environment jsdom
33
*/
4-
import { act } from 'react'
4+
import { act, type ReactNode } from 'react'
55
import { createRoot, type Root } from 'react-dom/client'
66
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
77

@@ -10,10 +10,35 @@ const { mockToastError } = vi.hoisted(() => ({
1010
}))
1111

1212
vi.mock('@sim/emcn', () => ({
13+
Loader: () => <span aria-hidden='true' />,
14+
Modal: ({
15+
children,
16+
open,
17+
onOpenChange,
18+
}: {
19+
children: ReactNode
20+
open: boolean
21+
onOpenChange: (open: boolean) => void
22+
}) =>
23+
open ? (
24+
<div>
25+
{children}
26+
<button type='button' onClick={() => onOpenChange(false)}>
27+
Close
28+
</button>
29+
</div>
30+
) : null,
31+
ModalBody: ({ children }: { children: ReactNode }) => <div>{children}</div>,
32+
ModalContent: ({ children }: { children: ReactNode }) => <div>{children}</div>,
33+
ModalDescription: ({ children }: { children: ReactNode }) => <p>{children}</p>,
34+
ModalHeader: ({ children }: { children: ReactNode }) => <h2>{children}</h2>,
1335
toast: { error: mockToastError },
1436
}))
1537

16-
import { SnapshotBoundary } from '@/app/workspace/[workspaceId]/logs/components/log-details/components/execution-snapshot/snapshot-boundary'
38+
import {
39+
SnapshotBoundary,
40+
SnapshotModalFallback,
41+
} from '@/app/workspace/[workspaceId]/logs/components/log-details/components/execution-snapshot/snapshot-boundary'
1742

1843
const LOAD_ERROR = new Error('snapshot chunk failed')
1944

@@ -72,4 +97,20 @@ describe('SnapshotBoundary', () => {
7297
)
7398
expect(onLoadError).toHaveBeenCalledOnce()
7499
})
100+
101+
it('keeps the modal shell visible while the snapshot bundle loads', () => {
102+
const onClose = vi.fn()
103+
104+
act(() => {
105+
root.render(<SnapshotModalFallback isOpen onClose={onClose} />)
106+
})
107+
108+
expect(container.textContent).toContain('Workflow State')
109+
expect(container.textContent).toContain('Loading run snapshot…')
110+
111+
const closeButton = container.querySelector('button')
112+
expect(closeButton).not.toBeNull()
113+
act(() => closeButton?.click())
114+
expect(onClose).toHaveBeenCalledOnce()
115+
})
75116
})

apps/sim/app/workspace/[workspaceId]/logs/components/log-details/components/execution-snapshot/snapshot-boundary.tsx

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,15 @@
11
'use client'
22

33
import { Component, type ErrorInfo, type ReactNode } from 'react'
4-
import { toast } from '@sim/emcn'
4+
import {
5+
Loader,
6+
Modal,
7+
ModalBody,
8+
ModalContent,
9+
ModalDescription,
10+
ModalHeader,
11+
toast,
12+
} from '@sim/emcn'
513
import { createLogger } from '@sim/logger'
614

715
const logger = createLogger('ExecutionSnapshotBoundary')
@@ -18,6 +26,35 @@ interface SnapshotBoundaryState {
1826

1927
const reportedErrors = new WeakSet<Error>()
2028

29+
interface SnapshotModalFallbackProps {
30+
isOpen: boolean
31+
onClose: () => void
32+
}
33+
34+
export function SnapshotModalFallback({ isOpen, onClose }: SnapshotModalFallbackProps) {
35+
return (
36+
<Modal
37+
open={isOpen}
38+
onOpenChange={(open) => {
39+
if (!open) onClose()
40+
}}
41+
>
42+
<ModalContent size='full' className='flex h-[90vh] flex-col'>
43+
<ModalHeader>Workflow State</ModalHeader>
44+
<ModalBody className='!p-0 flex min-h-0 flex-1 items-center justify-center overflow-hidden'>
45+
<ModalDescription className='sr-only'>
46+
Loading the workflow state snapshot for this execution
47+
</ModalDescription>
48+
<div className='flex items-center gap-2 text-[var(--text-secondary)]'>
49+
<Loader className='size-[16px]' animate />
50+
<span className='text-small'>Loading run snapshot…</span>
51+
</div>
52+
</ModalBody>
53+
</ModalContent>
54+
</Modal>
55+
)
56+
}
57+
2158
/**
2259
* Error boundary for the lazily loaded execution snapshot.
2360
*

apps/sim/app/workspace/[workspaceId]/logs/components/log-details/log-details.tsx

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,10 @@ import { DELETED_WORKFLOW_LABEL } from '@/lib/workflows/workflow-labels'
6363
* barrel forms a parent->child cycle that would keep the barrel edge to the snapshot
6464
* alive and silently defeat the ExecutionSnapshot lazy split below.
6565
*/
66-
import { SnapshotBoundary } from '@/app/workspace/[workspaceId]/logs/components/log-details/components/execution-snapshot/snapshot-boundary'
66+
import {
67+
SnapshotBoundary,
68+
SnapshotModalFallback,
69+
} from '@/app/workspace/[workspaceId]/logs/components/log-details/components/execution-snapshot/snapshot-boundary'
6770
import { FileCards } from '@/app/workspace/[workspaceId]/logs/components/log-details/components/file-download'
6871
import { TraceView } from '@/app/workspace/[workspaceId]/logs/components/log-details/components/trace-view'
6972
import { useLogDetailsResize } from '@/app/workspace/[workspaceId]/logs/hooks'
@@ -708,7 +711,14 @@ export function LogDetailsContent({ log, onActiveTabChange }: LogDetailsContentP
708711
isOpen={isExecutionSnapshotOpen}
709712
onLoadError={() => setIsExecutionSnapshotOpen(false)}
710713
>
711-
<Suspense fallback={null}>
714+
<Suspense
715+
fallback={
716+
<SnapshotModalFallback
717+
isOpen={isExecutionSnapshotOpen}
718+
onClose={() => setIsExecutionSnapshotOpen(false)}
719+
/>
720+
}
721+
>
712722
<ExecutionSnapshot
713723
executionId={log.executionId}
714724
traceSpans={traceSpans}

apps/sim/app/workspace/[workspaceId]/logs/logs.tsx

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,10 @@ import {
6666
type ResourceTableHandle,
6767
} from '@/app/workspace/[workspaceId]/components'
6868
import { LogsEmptyState } from '@/app/workspace/[workspaceId]/components/resource/components/resource-empty-state'
69-
import { SnapshotBoundary } from '@/app/workspace/[workspaceId]/logs/components/log-details/components/execution-snapshot/snapshot-boundary'
69+
import {
70+
SnapshotBoundary,
71+
SnapshotModalFallback,
72+
} from '@/app/workspace/[workspaceId]/logs/components/log-details/components/execution-snapshot/snapshot-boundary'
7073
import { useLogFilters } from '@/app/workspace/[workspaceId]/logs/hooks/use-log-filters'
7174
import { useSearchState } from '@/app/workspace/[workspaceId]/logs/hooks/use-search-state'
7275
import {
@@ -1281,7 +1284,7 @@ export default function Logs() {
12811284
isOpen
12821285
onLoadError={handleClosePreview}
12831286
>
1284-
<Suspense fallback={null}>
1287+
<Suspense fallback={<SnapshotModalFallback isOpen onClose={handleClosePreview} />}>
12851288
<ExecutionSnapshot
12861289
executionId={previewDetailQuery.data.executionId}
12871290
traceSpans={previewDetailQuery.data.executionData?.traceSpans}

0 commit comments

Comments
 (0)