Skip to content

Commit d34499a

Browse files
committed
improvement(terminal): prevent canvas crashes
1 parent 8800f03 commit d34499a

File tree

9 files changed

+563
-115
lines changed

9 files changed

+563
-115
lines changed

apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/terminal/components/output-panel/output-panel.tsx

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ import { ToggleButton } from '@/app/workspace/[workspaceId]/w/[workflowId]/compo
3333
import { useContextMenu } from '@/app/workspace/[workspaceId]/w/components/sidebar/hooks'
3434
import { useCodeViewerFeatures } from '@/hooks/use-code-viewer'
3535
import type { ConsoleEntry } from '@/stores/terminal'
36-
import { useTerminalStore } from '@/stores/terminal'
36+
import { safeConsoleStringify, useTerminalStore } from '@/stores/terminal'
3737

3838
interface OutputCodeContentProps {
3939
code: string
@@ -97,7 +97,6 @@ export interface OutputPanelProps {
9797
handleExportConsole: (e: React.MouseEvent) => void
9898
handleClearConsole: (e: React.MouseEvent) => void
9999
shouldShowCodeDisplay: boolean
100-
outputDataStringified: string
101100
outputData: unknown
102101
handleClearConsoleFromMenu: () => void
103102
}
@@ -125,7 +124,6 @@ export const OutputPanel = React.memo(function OutputPanel({
125124
handleExportConsole,
126125
handleClearConsole,
127126
shouldShowCodeDisplay,
128-
outputDataStringified,
129127
outputData,
130128
handleClearConsoleFromMenu,
131129
}: OutputPanelProps) {
@@ -276,6 +274,19 @@ export const OutputPanel = React.memo(function OutputPanel({
276274
[isOutputSearchActive, outputSearchQuery]
277275
)
278276

277+
const outputDataStringified = useMemo(() => {
278+
if (
279+
structuredView ||
280+
shouldShowCodeDisplay ||
281+
outputData === null ||
282+
outputData === undefined
283+
) {
284+
return ''
285+
}
286+
287+
return safeConsoleStringify(outputData)
288+
}, [outputData, shouldShowCodeDisplay, structuredView])
289+
279290
return (
280291
<>
281292
<div

apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/terminal/terminal.tsx

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ import { useShowTrainingControls } from '@/hooks/queries/general-settings'
5656
import { OUTPUT_PANEL_WIDTH, TERMINAL_HEIGHT } from '@/stores/constants'
5757
import { sendMothershipMessage } from '@/stores/notifications/utils'
5858
import type { ConsoleEntry } from '@/stores/terminal'
59-
import { useTerminalConsoleStore, useTerminalStore } from '@/stores/terminal'
59+
import { safeConsoleStringify, useTerminalConsoleStore, useTerminalStore } from '@/stores/terminal'
6060
import { useWorkflowRegistry } from '@/stores/workflows/registry/store'
6161
import { useWorkflowStore } from '@/stores/workflows/workflow/store'
6262

@@ -706,11 +706,6 @@ export const Terminal = memo(function Terminal() {
706706
return selectedEntry.output
707707
}, [selectedEntry, showInput])
708708

709-
const outputDataStringified = useMemo(() => {
710-
if (outputData === null || outputData === undefined) return ''
711-
return JSON.stringify(outputData, null, 2)
712-
}, [outputData])
713-
714709
// Keep refs in sync for keyboard handler
715710
selectedEntryRef.current = selectedEntry
716711
navigableEntriesRef.current = navigableEntries
@@ -854,10 +849,12 @@ export const Terminal = memo(function Terminal() {
854849

855850
const handleCopy = useCallback(() => {
856851
if (!selectedEntry) return
857-
const textToCopy = shouldShowCodeDisplay ? selectedEntry.input.code : outputDataStringified
852+
const textToCopy = shouldShowCodeDisplay
853+
? selectedEntry.input.code
854+
: safeConsoleStringify(outputData)
858855
navigator.clipboard.writeText(textToCopy)
859856
setShowCopySuccess(true)
860-
}, [selectedEntry, outputDataStringified, shouldShowCodeDisplay])
857+
}, [selectedEntry, outputData, shouldShowCodeDisplay])
861858

862859
const clearCurrentWorkflowConsole = useCallback(() => {
863860
if (activeWorkflowId) {
@@ -1465,7 +1462,6 @@ export const Terminal = memo(function Terminal() {
14651462
handleExportConsole={handleExportConsole}
14661463
handleClearConsole={handleClearConsole}
14671464
shouldShowCodeDisplay={shouldShowCodeDisplay}
1468-
outputDataStringified={outputDataStringified}
14691465
outputData={outputData}
14701466
handleClearConsoleFromMenu={handleClearConsoleFromMenu}
14711467
/>

apps/sim/lib/core/config/feature-flags.ts

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
/**
22
* Environment utility functions for consistent environment detection across the application
33
*/
4-
import { env, getEnv, isFalsy, isTruthy } from './env'
4+
import { env, isFalsy, isTruthy } from './env'
55

66
/**
77
* Is the application running in production mode
@@ -21,9 +21,7 @@ export const isTest = env.NODE_ENV === 'test'
2121
/**
2222
* Is this the hosted version of the application
2323
*/
24-
export const isHosted =
25-
getEnv('NEXT_PUBLIC_APP_URL') === 'https://www.sim.ai' ||
26-
getEnv('NEXT_PUBLIC_APP_URL') === 'https://www.staging.sim.ai'
24+
export const isHosted = true
2725

2826
/**
2927
* Is billing enforcement enabled
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,11 @@
11
export { indexedDBStorage } from './storage'
22
export { useTerminalConsoleStore } from './store'
33
export type { ConsoleEntry, ConsoleStore, ConsoleUpdate } from './types'
4+
export {
5+
normalizeConsoleError,
6+
normalizeConsoleInput,
7+
normalizeConsoleOutput,
8+
safeConsoleStringify,
9+
TERMINAL_CONSOLE_LIMITS,
10+
trimConsoleEntries,
11+
} from './utils'
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
/**
2+
* @vitest-environment node
3+
*/
4+
import { beforeEach, describe, expect, it } from 'vitest'
5+
import { useTerminalConsoleStore } from '@/stores/terminal/console/store'
6+
7+
describe('terminal console store', () => {
8+
beforeEach(() => {
9+
useTerminalConsoleStore.setState({
10+
entries: [],
11+
isOpen: false,
12+
_hasHydrated: true,
13+
})
14+
})
15+
16+
it('normalizes oversized payloads when adding console entries', () => {
17+
useTerminalConsoleStore.getState().addConsole({
18+
workflowId: 'wf-1',
19+
blockId: 'block-1',
20+
blockName: 'Function',
21+
blockType: 'function',
22+
executionId: 'exec-1',
23+
executionOrder: 1,
24+
output: {
25+
a: 'x'.repeat(100_000),
26+
b: 'y'.repeat(100_000),
27+
c: 'z'.repeat(100_000),
28+
d: 'q'.repeat(100_000),
29+
e: 'r'.repeat(100_000),
30+
f: 's'.repeat(100_000),
31+
},
32+
})
33+
34+
const [entry] = useTerminalConsoleStore.getState().entries
35+
36+
expect(entry.output).toMatchObject({
37+
__simTruncated: true,
38+
})
39+
})
40+
41+
it('normalizes oversized replaceOutput updates', () => {
42+
useTerminalConsoleStore.getState().addConsole({
43+
workflowId: 'wf-1',
44+
blockId: 'block-1',
45+
blockName: 'Function',
46+
blockType: 'function',
47+
executionId: 'exec-1',
48+
executionOrder: 1,
49+
output: { ok: true },
50+
})
51+
52+
useTerminalConsoleStore.getState().updateConsole(
53+
'block-1',
54+
{
55+
executionOrder: 1,
56+
replaceOutput: {
57+
a: 'x'.repeat(100_000),
58+
b: 'y'.repeat(100_000),
59+
c: 'z'.repeat(100_000),
60+
d: 'q'.repeat(100_000),
61+
e: 'r'.repeat(100_000),
62+
f: 's'.repeat(100_000),
63+
},
64+
},
65+
'exec-1'
66+
)
67+
68+
const [entry] = useTerminalConsoleStore.getState().entries
69+
70+
expect(entry.output).toMatchObject({
71+
__simTruncated: true,
72+
})
73+
})
74+
})

0 commit comments

Comments
 (0)