@@ -42,7 +42,7 @@ const TUIApp = ({
42
42
const logIdCounter = useRef ( 0 )
43
43
const { stdout } = useStdout ( )
44
44
const ctrlCMessageDefault = "^C quit"
45
- const [ ctrlCMessage ] = useState ( ctrlCMessageDefault )
45
+ const [ ctrlCMessage , setCtrlCMessage ] = useState ( ctrlCMessageDefault )
46
46
47
47
const [ terminalSize , setTerminalSize ] = useState ( ( ) => ( {
48
48
width : stdout ?. columns || 80 ,
@@ -82,7 +82,21 @@ const TUIApp = ({
82
82
83
83
// Provide status update function to parent
84
84
useEffect ( ( ) => {
85
- onStatusUpdate ( setInitStatus )
85
+ onStatusUpdate ( ( status : string | null ) => {
86
+ // Check if this is the "Press Ctrl+C again" warning
87
+ if ( status ?. includes ( "Press Ctrl+C again" ) ) {
88
+ // Update the bottom Ctrl+C message with warning emoji
89
+ setCtrlCMessage ( "⚠️ ^C again to quit" )
90
+ // Clear the init status since we don't want it in the header anymore
91
+ setInitStatus ( null )
92
+ // Reset after 3 seconds
93
+ setTimeout ( ( ) => {
94
+ setCtrlCMessage ( ctrlCMessageDefault )
95
+ } , 3000 )
96
+ } else {
97
+ setInitStatus ( status )
98
+ }
99
+ } )
86
100
} , [ onStatusUpdate ] )
87
101
88
102
// Calculate available lines for logs dynamically based on terminal height and mode
@@ -94,13 +108,22 @@ const TUIApp = ({
94
108
// In compact mode, reduce header size, account for bottom status line
95
109
return Math . max ( 3 , termHeight - 10 )
96
110
} else {
97
- // Normal mode calculation - reduced header lines since we removed Controls section
98
- const headerLines = 10 // Reduced from 12 since Controls section is commented out
99
- const logBoxHeaderLines = 3
100
- const logBoxFooterLines = scrollOffset > 0 ? 3 : 1
101
- const bottomStatusLine = 1 // Just one line for the log path
102
- const safetyBuffer = 0 // Removed safety buffer to use more space
103
- const totalReservedLines = headerLines + logBoxHeaderLines + logBoxFooterLines + bottomStatusLine + safetyBuffer
111
+ // Normal mode calculation - account for all UI elements
112
+ const headerBorderLines = 2 // Top border (with title) + bottom border
113
+ const headerContentLines = 5 // Logo is 4 lines tall, +1 for padding
114
+ const logBoxBorderLines = 2 // Top and bottom border of log box
115
+ const logBoxHeaderLines = 2 // "Logs (X total)" text (no blank line after)
116
+ const logBoxFooterLines = scrollOffset > 0 ? 2 : 0 // "(X lines below)" when scrolled
117
+ const bottomStatusLine = 1 // Log path and quit message
118
+ const safetyBuffer = 1 // Small buffer to prevent header from being pushed up
119
+ const totalReservedLines =
120
+ headerBorderLines +
121
+ headerContentLines +
122
+ logBoxBorderLines +
123
+ logBoxHeaderLines +
124
+ logBoxFooterLines +
125
+ bottomStatusLine +
126
+ safetyBuffer
104
127
return Math . max ( 3 , termHeight - totalReservedLines )
105
128
}
106
129
}
@@ -237,41 +260,52 @@ const TUIApp = ({
237
260
)
238
261
239
262
// Render normal header
240
- const renderNormalHeader = ( ) => (
241
- < Box borderStyle = "round" borderColor = "#A18CE5" paddingX = { 2 } paddingY = { 1 } marginBottom = { 1 } flexDirection = "column" >
242
- < Box flexDirection = "row" gap = { 3 } >
243
- { /* ASCII Logo on the left */ }
244
- { /* biome-ignore format: preserve ASCII art alignment */ }
245
- < Box flexDirection = "column" alignItems = "flex-start" >
246
- { FULL_LOGO . map ( ( line ) => (
247
- < Text key = { line } color = "#A18CE5" bold > { line } </ Text >
248
- ) ) }
249
- </ Box >
250
-
251
- { /* Info on the right */ }
252
- < Box flexDirection = "column" flexGrow = { 1 } >
253
- < Text color = "#A18CE5" bold >
254
- { commandName } v{ version } { initStatus ? `- ${ initStatus } ` : "is running!" }
255
- </ Text >
256
- < Text > </ Text >
257
- < Text color = "cyan" > 🌐 Your App: http://localhost:{ appPort } </ Text >
258
- < Text color = "cyan" > 🤖 MCP Server: http://localhost:{ mcpPort } /mcp</ Text >
259
- < Text color = "cyan" >
260
- 📸 Visual Timeline: http://localhost:{ mcpPort } /logs
261
- { projectName ? `?project=${ encodeURIComponent ( projectName ) } ` : "" }
262
- </ Text >
263
- { serversOnly && < Text color = "cyan" > 🖥️ Servers-only mode - use Chrome extension for browser monitoring</ Text > }
263
+ const renderNormalHeader = ( ) => {
264
+ // Create custom top border with title embedded (like Claude Code)
265
+ const title = ` ${ commandName } v${ version } ${ initStatus ? `- ${ initStatus } ` : "" } `
266
+ const borderChar = "─"
267
+ const leftPadding = 2
268
+ // Account for border characters and padding
269
+ const availableWidth = termWidth - 2 // -2 for corner characters
270
+ const titleLength = title . length
271
+ const rightBorderLength = Math . max ( 0 , availableWidth - titleLength - leftPadding )
272
+ const topBorderLine = `╭${ borderChar . repeat ( leftPadding ) } ${ title } ${ borderChar . repeat ( rightBorderLength ) } ╮`
273
+
274
+ return (
275
+ < Box flexDirection = "column" >
276
+ { /* Custom top border with embedded title */ }
277
+ < Text color = "#A18CE5" > { topBorderLine } </ Text >
278
+
279
+ { /* Content with side borders only */ }
280
+ < Box borderStyle = "round" borderColor = "#A18CE5" borderTop = { false } paddingX = { 2 } paddingY = { 1 } >
281
+ < Box flexDirection = "row" gap = { 3 } >
282
+ { /* ASCII Logo on the left */ }
283
+ { /* biome-ignore format: preserve ASCII art alignment */ }
284
+ < Box flexDirection = "column" alignItems = "flex-start" >
285
+ { FULL_LOGO . map ( ( line ) => (
286
+ < Text key = { line } color = "#A18CE5" bold >
287
+ { line }
288
+ </ Text >
289
+ ) ) }
290
+ </ Box >
291
+
292
+ { /* Info on the right */ }
293
+ < Box flexDirection = "column" flexGrow = { 1 } >
294
+ < Text color = "cyan" > 🌐 Your App: http://localhost:{ appPort } </ Text >
295
+ < Text color = "cyan" > 🤖 MCP Server: http://localhost:{ mcpPort } /mcp</ Text >
296
+ < Text color = "cyan" >
297
+ 📸 Visual Timeline: http://localhost:{ mcpPort } /logs
298
+ { projectName ? `?project=${ encodeURIComponent ( projectName ) } ` : "" }
299
+ </ Text >
300
+ { serversOnly && (
301
+ < Text color = "cyan" > 🖥️ Servers-only mode - use Chrome extension for browser monitoring</ Text >
302
+ ) }
303
+ </ Box >
304
+ </ Box >
264
305
</ Box >
265
306
</ Box >
266
-
267
- { /* Controls at the bottom of header box */ }
268
- { /*
269
- <Box marginTop={1}>
270
- <Text dimColor>💡 Controls: ↑/↓ scroll | PgUp/PgDn page | g/G start/end | Ctrl-C quit</Text>
271
- </Box>
272
- */ }
273
- </ Box >
274
- )
307
+ )
308
+ }
275
309
276
310
return (
277
311
< Box flexDirection = "column" height = "100%" >
@@ -281,12 +315,9 @@ const TUIApp = ({
281
315
{ /* Logs Box - flexGrow makes it expand to fill available height */ }
282
316
< Box flexDirection = "column" borderStyle = "single" borderColor = "gray" paddingX = { 1 } flexGrow = { 1 } minHeight = { 0 } >
283
317
{ ! isVeryCompact && (
284
- < >
285
- < Text color = "gray" dimColor >
286
- Logs ({ logs . length } total{ scrollOffset > 0 && `, scrolled up ${ scrollOffset } lines` } )
287
- </ Text >
288
- < Text > </ Text >
289
- </ >
318
+ < Text color = "gray" dimColor >
319
+ Logs ({ logs . length } total{ scrollOffset > 0 && `, scrolled up ${ scrollOffset } lines` } )
320
+ </ Text >
290
321
) }
291
322
292
323
{ /* Logs content area - also uses flexGrow to expand */ }
@@ -401,10 +432,7 @@ const TUIApp = ({
401
432
402
433
{ /* Scroll indicator - only show when scrolled up and not in very compact mode */ }
403
434
{ ! isVeryCompact && logs . length > maxVisibleLogs && scrollOffset > 0 && (
404
- < >
405
- < Text > </ Text >
406
- < Text dimColor > ({ scrollOffset } lines below)</ Text >
407
- </ >
435
+ < Text dimColor > ({ scrollOffset } lines below)</ Text >
408
436
) }
409
437
</ Box >
410
438
0 commit comments