Skip to content

Commit b8fccb2

Browse files
committed
feat(terminal): height memory
1 parent fcdc117 commit b8fccb2

File tree

7 files changed

+146
-50
lines changed

7 files changed

+146
-50
lines changed

.cursorrules

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ ENSURE that you use the logger.info and logger.warn and logger.error instead of
88

99
## Comments
1010

11-
You must use TSDOC for comments. Do not use ==== for comments to separate sections.
11+
You must use TSDOC for comments. Do not use ==== for comments to separate sections. Do not leave any comments that are not TSDOC.
1212

1313
## Globals styles
1414

apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/diff-controls/diff-controls.tsx

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
import { memo, useCallback } from 'react'
2+
import clsx from 'clsx'
23
import { Eye, EyeOff } from 'lucide-react'
34
import { Button } from '@/components/emcn'
45
import { createLogger } from '@/lib/logs/console/logger'
56
import { useCopilotStore } from '@/stores/panel/copilot/store'
7+
import { useTerminalStore } from '@/stores/terminal'
68
import { useWorkflowDiffStore } from '@/stores/workflow-diff'
79
import { useWorkflowRegistry } from '@/stores/workflows/registry/store'
810
import { mergeSubblockState } from '@/stores/workflows/utils'
@@ -11,6 +13,7 @@ import { useWorkflowStore } from '@/stores/workflows/workflow/store'
1113
const logger = createLogger('DiffControls')
1214

1315
export const DiffControls = memo(function DiffControls() {
16+
const isTerminalResizing = useTerminalStore((state) => state.isResizing)
1417
// Optimized: Single diff store subscription
1518
const {
1619
isShowingDiff,
@@ -312,7 +315,10 @@ export const DiffControls = memo(function DiffControls() {
312315

313316
return (
314317
<div
315-
className='-translate-x-1/2 fixed left-1/2 z-30'
318+
className={clsx(
319+
'-translate-x-1/2 fixed left-1/2 z-30',
320+
!isTerminalResizing && 'transition-[bottom] duration-100 ease-out'
321+
)}
316322
style={{ bottom: 'calc(var(--terminal-height) + 40px)' }}
317323
>
318324
<div className='flex items-center gap-[6px] rounded-[10px] p-[6px]'>

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

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { memo, useCallback } from 'react'
2+
import clsx from 'clsx'
23
import { X } from 'lucide-react'
34
import { useParams } from 'next/navigation'
45
import { Button } from '@/components/emcn'
@@ -10,6 +11,7 @@ import {
1011
openCopilotWithMessage,
1112
useNotificationStore,
1213
} from '@/stores/notifications'
14+
import { useTerminalStore } from '@/stores/terminal'
1315

1416
const logger = createLogger('Notifications')
1517
const MAX_VISIBLE_NOTIFICATIONS = 4
@@ -29,6 +31,7 @@ export const Notifications = memo(function Notifications() {
2931
const removeNotification = useNotificationStore((state) => state.removeNotification)
3032
const clearNotifications = useNotificationStore((state) => state.clearNotifications)
3133
const visibleNotifications = notifications.slice(0, MAX_VISIBLE_NOTIFICATIONS)
34+
const isTerminalResizing = useTerminalStore((state) => state.isResizing)
3235

3336
/**
3437
* Executes a notification action and handles side effects.
@@ -95,7 +98,12 @@ export const Notifications = memo(function Notifications() {
9598
}
9699

97100
return (
98-
<div className='fixed right-[calc(var(--panel-width)+16px)] bottom-[calc(var(--terminal-height)+16px)] z-30 flex flex-col items-end'>
101+
<div
102+
className={clsx(
103+
'fixed right-[calc(var(--panel-width)+16px)] bottom-[calc(var(--terminal-height)+16px)] z-30 flex flex-col items-end',
104+
!isTerminalResizing && 'transition-[bottom] duration-100 ease-out'
105+
)}
106+
>
99107
{[...visibleNotifications].reverse().map((notification, index, stacked) => {
100108
const depth = stacked.length - index - 1
101109
const xOffset = depth * 3

apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/terminal/hooks/use-terminal-resize.ts

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { useCallback, useEffect, useState } from 'react'
1+
import { useCallback, useEffect } from 'react'
22
import { useTerminalStore } from '@/stores/terminal'
33

44
/**
@@ -15,15 +15,14 @@ const MAX_HEIGHT_PERCENTAGE = 0.7 // 70% of viewport height
1515
* @returns Resize state and handlers
1616
*/
1717
export function useTerminalResize() {
18-
const { setTerminalHeight } = useTerminalStore()
19-
const [isResizing, setIsResizing] = useState(false)
18+
const { setTerminalHeight, isResizing, setIsResizing } = useTerminalStore()
2019

2120
/**
2221
* Handles mouse down on resize handle
2322
*/
2423
const handleMouseDown = useCallback(() => {
2524
setIsResizing(true)
26-
}, [])
25+
}, [setIsResizing])
2726

2827
/**
2928
* Setup resize event listeners and body styles when resizing
@@ -57,7 +56,7 @@ export function useTerminalResize() {
5756
document.body.style.cursor = ''
5857
document.body.style.userSelect = ''
5958
}
60-
}, [isResizing, setTerminalHeight])
59+
}, [isResizing, setTerminalHeight, setIsResizing])
6160

6261
return {
6362
isResizing,

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

Lines changed: 48 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -35,11 +35,7 @@ import {
3535
} from '@/app/workspace/[workspaceId]/w/[workflowId]/components/terminal/hooks'
3636
import { getBlock } from '@/blocks'
3737
import type { ConsoleEntry } from '@/stores/terminal'
38-
import {
39-
DEFAULT_TERMINAL_HEIGHT,
40-
useTerminalConsoleStore,
41-
useTerminalStore,
42-
} from '@/stores/terminal'
38+
import { useTerminalConsoleStore, useTerminalStore } from '@/stores/terminal'
4339
import { useWorkflowRegistry } from '@/stores/workflows/registry/store'
4440

4541
/**
@@ -254,9 +250,11 @@ const isEventFromEditableElement = (e: KeyboardEvent): boolean => {
254250
export function Terminal() {
255251
const terminalRef = useRef<HTMLElement>(null)
256252
const prevEntriesLengthRef = useRef(0)
253+
const prevWorkflowEntriesLengthRef = useRef(0)
257254
const {
258255
terminalHeight,
259256
setTerminalHeight,
257+
lastExpandedHeight,
260258
outputPanelWidth,
261259
setOutputPanelWidth,
262260
openOnRun,
@@ -301,6 +299,22 @@ export function Terminal() {
301299

302300
const isExpanded = terminalHeight > NEAR_MIN_THRESHOLD
303301

302+
/**
303+
* Expands the terminal to its last meaningful height, with safeguards:
304+
* - Never expands below {@link DEFAULT_EXPANDED_HEIGHT}.
305+
* - Never exceeds 70% of the viewport height.
306+
*/
307+
const expandToLastHeight = useCallback(() => {
308+
setIsToggling(true)
309+
const maxHeight = window.innerHeight * 0.7
310+
const desiredHeight = Math.max(
311+
lastExpandedHeight || DEFAULT_EXPANDED_HEIGHT,
312+
DEFAULT_EXPANDED_HEIGHT
313+
)
314+
const targetHeight = Math.min(desiredHeight, maxHeight)
315+
setTerminalHeight(targetHeight)
316+
}, [lastExpandedHeight, setTerminalHeight])
317+
304318
/**
305319
* Get all entries for current workflow (before filtering) for filter options
306320
*/
@@ -404,6 +418,28 @@ export function Terminal() {
404418
return selectedEntry.output
405419
}, [selectedEntry, showInput])
406420

421+
/**
422+
* Auto-open the terminal on new entries when "Open on run" is enabled.
423+
* This mirrors the header toggle behavior by using expandToLastHeight,
424+
* ensuring we always get the same smooth height transition.
425+
*/
426+
useEffect(() => {
427+
if (!openOnRun) {
428+
prevWorkflowEntriesLengthRef.current = allWorkflowEntries.length
429+
return
430+
}
431+
432+
const previousLength = prevWorkflowEntriesLengthRef.current
433+
const currentLength = allWorkflowEntries.length
434+
435+
// Only react when new entries are added for the active workflow
436+
if (currentLength > previousLength && terminalHeight <= MIN_HEIGHT) {
437+
expandToLastHeight()
438+
}
439+
440+
prevWorkflowEntriesLengthRef.current = currentLength
441+
}, [allWorkflowEntries.length, expandToLastHeight, openOnRun, terminalHeight])
442+
407443
/**
408444
* Handle row click - toggle if clicking same entry
409445
* Disables auto-selection when user manually selects, re-enables when deselecting
@@ -421,14 +457,13 @@ export function Terminal() {
421457
* Handle header click - toggle between expanded and collapsed
422458
*/
423459
const handleHeaderClick = useCallback(() => {
424-
setIsToggling(true)
425-
426460
if (isExpanded) {
461+
setIsToggling(true)
427462
setTerminalHeight(MIN_HEIGHT)
428463
} else {
429-
setTerminalHeight(DEFAULT_TERMINAL_HEIGHT)
464+
expandToLastHeight()
430465
}
431-
}, [isExpanded, setTerminalHeight])
466+
}, [expandToLastHeight, isExpanded, setTerminalHeight])
432467

433468
/**
434469
* Handle transition end - reset toggling state
@@ -628,10 +663,7 @@ export function Terminal() {
628663
e.preventDefault()
629664

630665
if (!isExpanded) {
631-
setIsToggling(true)
632-
const maxHeight = window.innerHeight * 0.7
633-
const targetHeight = Math.min(DEFAULT_EXPANDED_HEIGHT, maxHeight)
634-
setTerminalHeight(targetHeight)
666+
expandToLastHeight()
635667
}
636668

637669
if (e.key === 'ArrowLeft') {
@@ -647,7 +679,7 @@ export function Terminal() {
647679

648680
window.addEventListener('keydown', handleKeyDown)
649681
return () => window.removeEventListener('keydown', handleKeyDown)
650-
}, [selectedEntry, showInput, hasInputData, isExpanded])
682+
}, [expandToLastHeight, selectedEntry, showInput, hasInputData, isExpanded])
651683

652684
/**
653685
* Handle Escape to unselect and Enter to re-enable auto-selection
@@ -1188,10 +1220,7 @@ export function Terminal() {
11881220
onClick={(e) => {
11891221
e.stopPropagation()
11901222
if (!isExpanded) {
1191-
setIsToggling(true)
1192-
const maxHeight = window.innerHeight * 0.7
1193-
const targetHeight = Math.min(DEFAULT_EXPANDED_HEIGHT, maxHeight)
1194-
setTerminalHeight(targetHeight)
1223+
expandToLastHeight()
11951224
}
11961225
if (showInput) setShowInput(false)
11971226
}}
@@ -1209,10 +1238,7 @@ export function Terminal() {
12091238
onClick={(e) => {
12101239
e.stopPropagation()
12111240
if (!isExpanded) {
1212-
setIsToggling(true)
1213-
const maxHeight = window.innerHeight * 0.7
1214-
const targetHeight = Math.min(DEFAULT_EXPANDED_HEIGHT, maxHeight)
1215-
setTerminalHeight(targetHeight)
1241+
expandToLastHeight()
12161242
}
12171243
setShowInput(true)
12181244
}}

apps/sim/stores/terminal/console/store.ts

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ import { useExecutionStore } from '@/stores/execution/store'
77
import { useNotificationStore } from '@/stores/notifications'
88
import { useGeneralStore } from '@/stores/settings/general/store'
99
import type { ConsoleEntry, ConsoleStore, ConsoleUpdate } from '@/stores/terminal/console/types'
10-
import { DEFAULT_TERMINAL_HEIGHT, MIN_TERMINAL_HEIGHT, useTerminalStore } from '../store'
1110

1211
const logger = createLogger('TerminalConsoleStore')
1312

@@ -130,16 +129,6 @@ export const useTerminalConsoleStore = create<ConsoleStore>()(
130129
}
131130
}
132131

133-
// Auto-expand terminal when new entries are appended and it is minimized
134-
try {
135-
const { openOnRun, terminalHeight, setTerminalHeight } = useTerminalStore.getState()
136-
if (openOnRun && terminalHeight <= MIN_TERMINAL_HEIGHT) {
137-
setTerminalHeight(DEFAULT_TERMINAL_HEIGHT)
138-
}
139-
} catch (error) {
140-
logger.error('Failed to auto-expand terminal on new console entry', { error })
141-
}
142-
143132
return newEntry
144133
},
145134

0 commit comments

Comments
 (0)