@@ -5,6 +5,7 @@ const { renderSidebar } = require('./sidebar');
55const { renderSettingsScreen } = require ( './settings-render' ) ;
66const { CONTROL_KEY_HELP } = require ( './shortcuts' ) ;
77const { stripAnsi } = require ( './theme' ) ;
8+ const { renderWelcomePage } = require ( './welcome' ) ;
89const { runCockpitAction } = require ( './action-runner' ) ;
910const {
1011 PANE_MENU_ITEMS ,
@@ -22,7 +23,8 @@ const DEFAULT_SETTINGS = {
2223 defaultBase : 'main' ,
2324} ;
2425
25- const MODES = new Set ( [ 'details' , 'menu' , 'settings' ] ) ;
26+ const MODES = new Set ( [ 'main' , 'menu' , 'settings' , 'shortcuts' , 'new-agent' , 'terminal' ] ) ;
27+ const EMPTY_ACTION_ROWS = Object . freeze ( [ 'new-agent' , 'terminal' , 'settings' , 'shortcuts' ] ) ;
2628const SETTINGS_FIELDS = [
2729 'theme' ,
2830 'sidebarWidth' ,
@@ -150,8 +152,17 @@ function normalizeSettings(settings) {
150152 } ;
151153}
152154
155+ function normalizeActionRows ( rows ) {
156+ if ( ! Array . isArray ( rows ) || rows . length === 0 ) {
157+ return [ ...EMPTY_ACTION_ROWS ] ;
158+ }
159+ const normalized = rows . map ( ( row ) => text ( row ) ) . filter ( Boolean ) ;
160+ return normalized . length > 0 ? normalized : [ ...EMPTY_ACTION_ROWS ] ;
161+ }
162+
153163function normalizeMode ( mode ) {
154- return MODES . has ( mode ) ? mode : 'details' ;
164+ if ( mode === 'details' ) return 'main' ;
165+ return MODES . has ( mode ) ? mode : 'main' ;
155166}
156167
157168function normalizeControlState ( state = { } ) {
@@ -163,8 +174,10 @@ function normalizeControlState(state = {}) {
163174 : Array . isArray ( cockpitState . sessions )
164175 ? cockpitState . sessions
165176 : [ ] ;
177+ const actionRows = normalizeActionRows ( state . actionRows ) ;
166178 const selectedIndex = clampIndex ( number ( state . selectedIndex , 0 ) , sessions . length ) ;
167179 const selected = sessions [ selectedIndex ] || null ;
180+ const selectedScope = sessions . length > 0 ? 'lane' : 'action' ;
168181
169182 return {
170183 ...state ,
@@ -174,6 +187,9 @@ function normalizeControlState(state = {}) {
174187 sessions,
175188 selectedIndex,
176189 selectedSessionId : text ( state . selectedSessionId || ( selected && sessionId ( selected ) ) ) ,
190+ selectedScope,
191+ actionRows,
192+ actionIndex : wrapIndex ( number ( state . actionIndex , 0 ) , actionRows . length ) ,
177193 mode : normalizeMode ( state . mode ) ,
178194 menuIndex : wrapIndex ( number ( state . menuIndex , 0 ) , MENU_ITEMS . length ) ,
179195 settingsIndex : wrapIndex ( number ( state . settingsIndex , 0 ) , SETTINGS_FIELDS . length ) ,
@@ -266,7 +282,7 @@ function chooseMenuItem(state) {
266282 const intent = buildIntent ( current , result . actionId ) ;
267283 return normalizeControlState ( {
268284 ...current ,
269- mode : 'details ' ,
285+ mode : 'main ' ,
270286 paneMenuMessage : '' ,
271287 shouldExit : intent . type === 'quit' ,
272288 lastIntent : intent ,
@@ -290,9 +306,54 @@ function normalizeKey(value) {
290306 if ( raw === '\u001b[B' ) return 'down' ;
291307 if ( raw === '\t' ) return 'tab' ;
292308 if ( / ^ a l t (?: \+ | - ) ? s h i f t (?: \+ | - ) ? m $ / i. test ( raw ) ) return 'alt-shift-m' ;
309+ if ( / ^ ( e s c | e s c a p e ) $ / i. test ( raw ) ) return 'escape' ;
293310 return raw . toLowerCase ( ) ;
294311}
295312
313+ function moveSelection ( state , direction ) {
314+ const current = normalizeControlState ( state ) ;
315+ if ( current . sessions . length > 0 ) {
316+ return normalizeControlState ( {
317+ ...current ,
318+ selectedScope : 'lane' ,
319+ selectedIndex : wrapIndex ( current . selectedIndex + direction , current . sessions . length ) ,
320+ selectedSessionId : '' ,
321+ lastIntent : null ,
322+ } ) ;
323+ }
324+
325+ return normalizeControlState ( {
326+ ...current ,
327+ selectedScope : 'action' ,
328+ selectedIndex : 0 ,
329+ actionIndex : wrapIndex ( current . actionIndex + direction , current . actionRows . length ) ,
330+ selectedSessionId : '' ,
331+ lastIntent : null ,
332+ } ) ;
333+ }
334+
335+ function openActionRow ( state , actionId ) {
336+ const current = normalizeControlState ( state ) ;
337+ if ( actionId === 'new-agent' ) {
338+ return normalizeControlState ( { ...current , mode : 'new-agent' , lastIntent : null } ) ;
339+ }
340+ if ( actionId === 'terminal' ) {
341+ return normalizeControlState ( { ...current , mode : 'terminal' , lastIntent : null } ) ;
342+ }
343+ if ( actionId === 'settings' ) {
344+ return normalizeControlState ( { ...current , mode : 'settings' , lastIntent : null } ) ;
345+ }
346+ if ( actionId === 'shortcuts' ) {
347+ return normalizeControlState ( { ...current , mode : 'shortcuts' , lastIntent : null } ) ;
348+ }
349+ return normalizeControlState ( { ...current , lastIntent : null } ) ;
350+ }
351+
352+ function openSelectedActionRow ( state ) {
353+ const current = normalizeControlState ( state ) ;
354+ return openActionRow ( current , current . actionRows [ current . actionIndex ] || current . actionRows [ 0 ] ) ;
355+ }
356+
296357function applyKey ( state , rawKey ) {
297358 const current = normalizeControlState ( state ) ;
298359 const key = normalizeKey ( rawKey ) ;
@@ -303,15 +364,15 @@ function applyKey(state, rawKey) {
303364 if ( result . action === 'cancel' ) {
304365 return normalizeControlState ( {
305366 ...current ,
306- mode : 'details ' ,
367+ mode : 'main ' ,
307368 paneMenuMessage : '' ,
308369 lastIntent : null ,
309370 } ) ;
310371 }
311372 if ( result . action === 'select' ) {
312373 return normalizeControlState ( {
313374 ...current ,
314- mode : 'details ' ,
375+ mode : 'main ' ,
315376 menuIndex : result . state . selectedIndex ,
316377 paneMenuMessage : '' ,
317378 lastIntent : buildIntent ( current , result . actionId ) ,
@@ -335,26 +396,11 @@ function applyKey(state, rawKey) {
335396 if ( key === 'escape' ) {
336397 return normalizeControlState ( {
337398 ...current ,
338- mode : 'details' ,
339- lastIntent : null ,
340- } ) ;
341- }
342- if ( key === 's' ) {
343- return normalizeControlState ( {
344- ...current ,
345- mode : 'settings' ,
346- lastIntent : null ,
347- } ) ;
348- }
349- if ( key === 'm' || key === 'tab' || key === 'alt-shift-m' ) {
350- return normalizeControlState ( {
351- ...current ,
352- mode : 'menu' ,
353- paneMenuMessage : '' ,
399+ mode : 'main' ,
354400 lastIntent : null ,
355401 } ) ;
356402 }
357- if ( DIRECT_DETAIL_PANE_KEYS . has ( normalizePaneMenuKey ( rawKey ) ) ) {
403+ if ( mode === 'main' && DIRECT_DETAIL_PANE_KEYS . has ( normalizePaneMenuKey ( rawKey ) ) ) {
358404 const result = applyPaneMenuKey ( paneMenuStateFromControl ( current ) , rawKey ) ;
359405 if ( result . action === 'select' ) {
360406 return normalizeControlState ( {
@@ -369,6 +415,26 @@ function applyKey(state, rawKey) {
369415 lastIntent : null ,
370416 } ) ;
371417 }
418+ if ( key === 'n' ) {
419+ return openActionRow ( current , 'new-agent' ) ;
420+ }
421+ if ( key === 't' ) {
422+ return openActionRow ( current , 'terminal' ) ;
423+ }
424+ if ( key === '?' ) {
425+ return openActionRow ( current , 'shortcuts' ) ;
426+ }
427+ if ( key === 's' ) {
428+ return openActionRow ( current , 'settings' ) ;
429+ }
430+ if ( key === 'm' || key === 'tab' || key === 'alt-shift-m' ) {
431+ return normalizeControlState ( {
432+ ...current ,
433+ mode : 'menu' ,
434+ paneMenuMessage : '' ,
435+ lastIntent : null ,
436+ } ) ;
437+ }
372438 if ( key === 'enter' ) {
373439 if ( mode === 'menu' ) return chooseMenuItem ( current ) ;
374440 if ( mode === 'settings' ) {
@@ -377,10 +443,27 @@ function applyKey(state, rawKey) {
377443 lastIntent : buildIntent ( current , 'settings:edit' ) ,
378444 } ) ;
379445 }
446+ if ( mode === 'new-agent' ) {
447+ return normalizeControlState ( {
448+ ...current ,
449+ mode : 'main' ,
450+ lastIntent : buildIntent ( current , 'agent:start' ) ,
451+ } ) ;
452+ }
453+ if ( mode === 'terminal' ) {
454+ return normalizeControlState ( {
455+ ...current ,
456+ mode : 'main' ,
457+ lastIntent : buildIntent ( current , 'terminal:open' ) ,
458+ } ) ;
459+ }
460+ if ( current . sessions . length === 0 && current . selectedScope === 'action' ) {
461+ return openSelectedActionRow ( current ) ;
462+ }
380463 return normalizeControlState ( {
381464 ...current ,
382- mode : 'menu ' ,
383- lastIntent : null ,
465+ mode : 'main ' ,
466+ lastIntent : buildIntent ( current , 'view' ) ,
384467 } ) ;
385468 }
386469 if ( key === 'down' || key === 'j' ) {
@@ -390,7 +473,7 @@ function applyKey(state, rawKey) {
390473 if ( mode === 'settings' ) {
391474 return normalizeControlState ( { ...current , settingsIndex : current . settingsIndex + 1 , lastIntent : null } ) ;
392475 }
393- return normalizeControlState ( { ... current , selectedIndex : current . selectedIndex + 1 , selectedSessionId : '' , lastIntent : null } ) ;
476+ return moveSelection ( current , 1 ) ;
394477 }
395478 if ( key === 'up' || key === 'k' ) {
396479 if ( mode === 'menu' ) {
@@ -399,7 +482,7 @@ function applyKey(state, rawKey) {
399482 if ( mode === 'settings' ) {
400483 return normalizeControlState ( { ...current , settingsIndex : current . settingsIndex - 1 , lastIntent : null } ) ;
401484 }
402- return normalizeControlState ( { ... current , selectedIndex : current . selectedIndex - 1 , selectedSessionId : '' , lastIntent : null } ) ;
485+ return moveSelection ( current , - 1 ) ;
403486 }
404487
405488 return current ;
@@ -481,11 +564,21 @@ function selectedField(state) {
481564 return SETTINGS_FIELDS [ current . settingsIndex ] || SETTINGS_FIELDS [ 0 ] ;
482565}
483566
567+ function welcomeState ( state ) {
568+ const current = normalizeControlState ( state ) ;
569+ return {
570+ ...current . cockpitState ,
571+ repoPath : current . repoPath ,
572+ baseBranch : current . baseBranch ,
573+ sessions : current . sessions ,
574+ } ;
575+ }
576+
484577function renderDetailsPanel ( state ) {
485578 const current = normalizeControlState ( state ) ;
486579 const session = selectedSession ( current ) ;
487580 const lines = [
488- 'details ' ,
581+ 'main ' ,
489582 `repo: ${ current . repoPath || '-' } ` ,
490583 `base: ${ current . baseBranch || '-' } ` ,
491584 `mode: ${ current . mode } ` ,
@@ -517,6 +610,54 @@ function renderDetailsPanel(state) {
517610 return `${ lines . join ( '\n' ) } \n` ;
518611}
519612
613+ function renderShortcutsPanel ( ) {
614+ return [
615+ 'shortcuts' ,
616+ '' ,
617+ 'j/down: next lane' ,
618+ 'k/up: previous lane' ,
619+ 'enter: view selected lane / open selected action' ,
620+ 'n: new agent' ,
621+ 't: terminal' ,
622+ 'm or Alt+Shift+M: pane menu' ,
623+ 's: settings' ,
624+ 'v/h/x/p/r/c/o/a/b/f/T/A: pane actions' ,
625+ 'esc: back to main' ,
626+ 'q: quit' ,
627+ '' ,
628+ ] . join ( '\n' ) ;
629+ }
630+
631+ function renderNewAgentPanel ( state ) {
632+ const current = normalizeControlState ( state ) ;
633+ return [
634+ 'new agent' ,
635+ '' ,
636+ `agent: ${ current . settings . defaultAgent } ` ,
637+ `base: ${ current . settings . defaultBase } ` ,
638+ '' ,
639+ 'Enter: open a guarded agent lane in Kitty' ,
640+ 'Esc: back to main' ,
641+ '' ,
642+ ] . join ( '\n' ) ;
643+ }
644+
645+ function renderTerminalPanel ( state ) {
646+ const current = normalizeControlState ( state ) ;
647+ const session = selectedSession ( current ) ;
648+ return [
649+ 'terminal' ,
650+ '' ,
651+ session
652+ ? `target: ${ sessionId ( session ) || text ( session . branch , 'selected lane' ) } `
653+ : `target: ${ current . repoPath || 'repo' } ` ,
654+ '' ,
655+ 'Enter: open Kitty terminal' ,
656+ 'Esc: back to main' ,
657+ '' ,
658+ ] . join ( '\n' ) ;
659+ }
660+
520661function renderMenuPanel ( state ) {
521662 const current = normalizeControlState ( state ) ;
522663 return renderPaneMenu ( paneMenuStateFromControl ( current ) , { width : 72 , theme : current . settings . theme } ) ;
@@ -534,6 +675,12 @@ function renderPanel(state) {
534675 const current = normalizeControlState ( state ) ;
535676 if ( current . mode === 'menu' ) return renderMenuPanel ( current ) ;
536677 if ( current . mode === 'settings' ) return renderSettingsPanel ( current ) ;
678+ if ( current . mode === 'shortcuts' ) return renderShortcutsPanel ( current ) ;
679+ if ( current . mode === 'new-agent' ) return renderNewAgentPanel ( current ) ;
680+ if ( current . mode === 'terminal' ) return renderTerminalPanel ( current ) ;
681+ if ( current . sessions . length === 0 ) {
682+ return renderWelcomePage ( welcomeState ( current ) , current . settings ) ;
683+ }
537684 return renderDetailsPanel ( current ) ;
538685}
539686
@@ -542,7 +689,7 @@ function renderControlFrame(state) {
542689 const width = number ( current . settings . sidebarWidth , DEFAULT_SETTINGS . sidebarWidth ) ;
543690 const sidebar = splitLines ( renderSidebar ( current , { width, theme : current . settings . theme } ) ) ;
544691 const framePanelState = current . mode === 'menu'
545- ? normalizeControlState ( { ...current , mode : 'details ' } )
692+ ? normalizeControlState ( { ...current , mode : 'main ' } )
546693 : current ;
547694 const panel = splitLines ( renderPanel ( framePanelState ) ) ;
548695 const leftWidth = Math . max ( width , ...sidebar . map ( ( line ) => stripAnsi ( line ) . length ) ) ;
@@ -704,6 +851,7 @@ module.exports = {
704851 MENU_ITEMS ,
705852 SETTINGS_FIELDS ,
706853 applyCockpitAction,
854+ applyCockpitKey : applyKey ,
707855 buildCockpitActionContext,
708856 normalizeControlState,
709857 normalizeKey,
0 commit comments