@@ -9,16 +9,20 @@ import { colors } from "../utils/theme.js";
99import { execCommand } from "../utils/exec.js" ;
1010import { useExitOnCtrlC } from "../hooks/useExitOnCtrlC.js" ;
1111import { useUpdateCheck } from "../hooks/useUpdateCheck.js" ;
12+ import { useBetaFeatures } from "../store/betaFeatureStore.js" ;
13+ import type { BetaFeature } from "../store/betaFeatureStore.js" ;
1214
1315interface MenuItem {
1416 key : string ;
1517 label : string ;
1618 description : string ;
1719 icon : string ;
1820 color : string ;
21+ /** If set, this item is only shown when the specified beta feature is enabled */
22+ betaFeature ?: BetaFeature ;
1923}
2024
21- const menuItems : MenuItem [ ] = [
25+ const allMenuItems : MenuItem [ ] = [
2226 {
2327 key : "devboxes" ,
2428 label : "Devboxes" ,
@@ -47,6 +51,14 @@ const menuItems: MenuItem[] = [
4751 icon : "▤" ,
4852 color : colors . secondary ,
4953 } ,
54+ {
55+ key : "benchmarks" ,
56+ label : "Benchmarks" ,
57+ description : "Performance testing and evaluation" ,
58+ icon : "▶" ,
59+ color : colors . success ,
60+ betaFeature : "benchmarks" ,
61+ } ,
5062 {
5163 key : "settings" ,
5264 label : "Settings" ,
@@ -72,10 +84,29 @@ function getLayoutMode(height: number): LayoutMode {
7284 return "minimal" ; // No banner + labels only
7385}
7486
87+ // Helper component for rendering beta badge
88+ const BetaBadge = ( ) => (
89+ < Text color = { colors . warning } bold >
90+ { " " }
91+ [BETA]
92+ </ Text >
93+ ) ;
94+
7595export const MainMenu = ( { onSelect } : MainMenuProps ) => {
7696 const { exit } = useApp ( ) ;
7797 const [ selectedIndex , setSelectedIndex ] = React . useState ( 0 ) ;
7898 const { stdout } = useStdout ( ) ;
99+ const { isFeatureEnabled } = useBetaFeatures ( ) ;
100+
101+ // Filter menu items based on beta feature flags
102+ const menuItems = React . useMemo ( ( ) => {
103+ return allMenuItems . filter ( ( item ) => {
104+ if ( item . betaFeature ) {
105+ return isFeatureEnabled ( item . betaFeature ) ;
106+ }
107+ return true ;
108+ } ) ;
109+ } , [ isFeatureEnabled ] ) ;
79110
80111 // Get raw terminal dimensions, responding to resize events
81112 // Default to 20 rows / 80 cols if we can't detect
@@ -117,6 +148,27 @@ export const MainMenu = ({ onSelect }: MainMenuProps) => {
117148 // Handle Ctrl+C to exit
118149 useExitOnCtrlC ( ) ;
119150
151+ // Helper to select menu item by key (if available in filtered list)
152+ const selectByKey = React . useCallback (
153+ ( key : string ) => {
154+ if ( menuItems . some ( ( item ) => item . key === key ) ) {
155+ onSelect ( key ) ;
156+ }
157+ } ,
158+ [ menuItems , onSelect ] ,
159+ ) ;
160+
161+ // Helper to select menu item by number (1-indexed, based on filtered list)
162+ const selectByNumber = React . useCallback (
163+ ( num : number ) => {
164+ const index = num - 1 ;
165+ if ( index >= 0 && index < menuItems . length ) {
166+ onSelect ( menuItems [ index ] . key ) ;
167+ }
168+ } ,
169+ [ menuItems , onSelect ] ,
170+ ) ;
171+
120172 useInput ( ( input , key ) => {
121173 if ( key . upArrow && selectedIndex > 0 ) {
122174 setSelectedIndex ( selectedIndex - 1 ) ;
@@ -126,16 +178,20 @@ export const MainMenu = ({ onSelect }: MainMenuProps) => {
126178 onSelect ( menuItems [ selectedIndex ] . key ) ;
127179 } else if ( key . escape ) {
128180 exit ( ) ;
129- } else if ( input === "d" || input === "1" ) {
130- onSelect ( "devboxes" ) ;
131- } else if ( input === "b" || input === "2" ) {
132- onSelect ( "blueprints" ) ;
133- } else if ( input === "s" || input === "3" ) {
134- onSelect ( "snapshots" ) ;
135- } else if ( input === "o" || input === "4" ) {
136- onSelect ( "objects" ) ;
137- } else if ( input === "n" || input === "5" ) {
138- onSelect ( "settings" ) ;
181+ } else if ( input === "d" ) {
182+ selectByKey ( "devboxes" ) ;
183+ } else if ( input === "b" ) {
184+ selectByKey ( "blueprints" ) ;
185+ } else if ( input === "s" ) {
186+ selectByKey ( "snapshots" ) ;
187+ } else if ( input === "o" ) {
188+ selectByKey ( "objects" ) ;
189+ } else if ( input === "e" ) {
190+ selectByKey ( "benchmarks" ) ;
191+ } else if ( input === "n" ) {
192+ selectByKey ( "settings" ) ;
193+ } else if ( input >= "1" && input <= "9" ) {
194+ selectByNumber ( parseInt ( input , 10 ) ) ;
139195 } else if ( input === "u" && updateAvailable ) {
140196 // Release terminal and exec into update command (never returns)
141197 execCommand ( "sh" , [
@@ -148,12 +204,13 @@ export const MainMenu = ({ onSelect }: MainMenuProps) => {
148204 const layoutMode = getLayoutMode ( terminalHeight ) ;
149205
150206 // Navigation tips for all layouts
207+ const quickSelectRange = `1-${ menuItems . length } ` ;
151208 const navTips = (
152209 < NavigationTips
153210 showArrows
154211 paddingX = { 2 }
155212 tips = { [
156- { key : "1-5" , label : "Quick select" } ,
213+ { key : quickSelectRange , label : "Quick select" } ,
157214 { key : "Enter" , label : "Select" } ,
158215 { key : "Esc" , label : "Quit" } ,
159216 { key : "u" , label : "Update" , condition : ! ! updateAvailable } ,
@@ -189,6 +246,7 @@ export const MainMenu = ({ onSelect }: MainMenuProps) => {
189246 >
190247 { item . label }
191248 </ Text >
249+ { item . betaFeature && < BetaBadge /> }
192250 < Text color = { colors . textDim } dimColor >
193251 { " " }
194252 [{ index + 1 } ]
@@ -234,6 +292,7 @@ export const MainMenu = ({ onSelect }: MainMenuProps) => {
234292 >
235293 { item . label }
236294 </ Text >
295+ { item . betaFeature && < BetaBadge /> }
237296 < Text color = { colors . textDim } dimColor >
238297 { isNarrow
239298 ? ` [${ index + 1 } ]`
@@ -285,6 +344,7 @@ export const MainMenu = ({ onSelect }: MainMenuProps) => {
285344 >
286345 { item . label }
287346 </ Text >
347+ { item . betaFeature && < BetaBadge /> }
288348 { ! isNarrow && (
289349 < Text color = { colors . textDim } dimColor >
290350 { " " }
@@ -346,6 +406,7 @@ export const MainMenu = ({ onSelect }: MainMenuProps) => {
346406 >
347407 { item . label }
348408 </ Text >
409+ { item . betaFeature && < BetaBadge /> }
349410 < Text color = { colors . textDim } dimColor >
350411 { " " }
351412 [{ index + 1 } ]
@@ -386,6 +447,7 @@ export const MainMenu = ({ onSelect }: MainMenuProps) => {
386447 >
387448 { item . label }
388449 </ Text >
450+ { item . betaFeature && < BetaBadge /> }
389451 < Text color = { colors . textDim } > </ Text >
390452 < Text color = { colors . textDim } dimColor >
391453 { item . description }
0 commit comments