Skip to content

Core MainMenu Refactor#13833

Merged
mayagbarnes merged 12 commits intofeature/new-app-menufrom
core-menu-refactor
Feb 9, 2026
Merged

Core MainMenu Refactor#13833
mayagbarnes merged 12 commits intofeature/new-app-menufrom
core-menu-refactor

Conversation

@mayagbarnes
Copy link
Copy Markdown
Collaborator

@mayagbarnes mayagbarnes commented Feb 5, 2026

Describe your changes

Core refactor of the MainMenu component that replaces BaseWeb's StatefulMenu with a custom data-driven implementation. This is the foundational PR for the MainMenu redesign, going into the feature branch for incremental review.

  • Remove StatefulMenu from BaseWeb, keep StatefulPopover for positioning/focus lock
  • Introduce section-based data model (MenuSection, MenuItemConfig)
  • Pure functions for menu construction (buildMenuData, buildMenuSections, buildCommonItems, etc.)
  • Memoized MenuContent component with stable callbacks

Note: Specific accessibility improvements will be made in a follow up PR. Should also get some bundle improvements in follow ups as well.

Before:
Screenshot 2026-02-03 at 7 38 57 p mScreenshot 2026-02-03 at 7 40 46 p m

After:
Updated - Regular OptionsScreenshot 2026-02-04 at 4 38 33 p m

Testing Plan

  • JS Unit Tests: ✅ Added & Updated
  • E2E Tests: ✅ Updated
  • Manual Testing: ✅ Checked local & Community Cloud

@mayagbarnes mayagbarnes added security-assessment-completed change:feature PR contains new feature or enhancement implementation impact:users PR changes affect end users labels Feb 5, 2026
@snyk-io
Copy link
Copy Markdown
Contributor

snyk-io bot commented Feb 5, 2026

Snyk checks have passed. No issues have been found so far.

Status Scanner Critical High Medium Low Total (0)
Open Source Security 0 0 0 0 0 issues
Licenses 0 0 0 0 0 issues

💻 Catch issues earlier using the plugins for VS Code, JetBrains IDEs, Visual Studio, and Eclipse.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Feb 5, 2026

✅ PR preview is ready!

Name Link
📦 Wheel file https://core-previews.s3-us-west-2.amazonaws.com/pr-13833/streamlit-1.53.1-py3-none-any.whl
📦 @streamlit/component-v2-lib Download from artifacts
🕹️ Preview app pr-13833.streamlit.app (☁️ Deploy here if not accessible)

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR implements a core refactor of the MainMenu component, replacing BaseWeb's StatefulMenu with a custom data-driven implementation. The refactor introduces a cleaner separation of concerns with pure functions for menu data construction and a section-based data model.

Changes:

  • Replaced BaseWeb's StatefulMenu with custom button-based menu items while retaining StatefulPopover for positioning
  • Introduced pure data model (MenuItemConfig, MenuSection) and builder functions for menu construction
  • Refactored styled components from complex nested selectors to simpler button-based styles
  • Updated all tests (unit and E2E) to work with the new implementation

Reviewed changes

Copilot reviewed 8 out of 17 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
frontend/lib/src/theme/primitives/sizes.ts Added appMainMenu size constant (12rem) for menu width
frontend/app/src/components/MainMenu/styled-components.ts Complete refactor: removed old BaseWeb-style overrides, added new button-based components (StyledMenuItemRow, StyledMenuItemContent, etc.)
frontend/app/src/components/MainMenu/MainMenu.tsx Core refactor: introduced data-driven architecture with pure builder functions, memoized components, and simplified logic
frontend/app/src/components/MainMenu/mainMenuTestHelpers.ts Updated test helpers to use userEvent and new test IDs, simplified API
frontend/app/src/components/MainMenu/MainMenu.test.tsx Comprehensive test updates with excellent coverage of all menu scenarios, proper use of async/await
frontend/app/src/App.test.tsx Updated to use new test helper API
e2e_playwright/st_set_page_config_test.py Updated E2E tests to use new menu structure and test IDs
e2e_playwright/main_menu_test.py Updated E2E tests, added Escape key test, fixed label references
e2e_playwright/__snapshots__/**/*.png Updated visual regression test snapshots

@mayagbarnes mayagbarnes added the ai-review If applied to PR or issue will run AI review workflow label Feb 5, 2026
@github-actions github-actions bot removed the ai-review If applied to PR or issue will run AI review workflow label Feb 5, 2026
@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Feb 5, 2026

Summary

This PR refactors the MainMenu component by replacing BaseWeb's StatefulMenu with a custom data-driven implementation. Key changes include:

  • Removing StatefulMenu dependency while keeping StatefulPopover for positioning/focus lock
  • Introducing a section-based data model (MenuSection, MenuItemConfig)
  • Creating pure functions for menu construction (buildMenuData, buildPrimaryItems, buildDevItems, etc.)
  • Simplifying styled components by removing unused BaseWeb-specific overrides
  • Updating all unit tests and e2e tests to match the new implementation

This is a foundational PR for a MainMenu redesign, with accessibility improvements noted for a follow-up PR.

Code Quality

The refactoring follows React best practices well:

Strengths:

  • Pure functions for menu construction make the logic easier to test and reason about
  • Data-driven approach with MenuSection[] cleanly separates data from rendering
  • Good use of useMemo for menu data construction (lines 470-502 in MainMenu.tsx)
  • Proper TypeScript interfaces (MenuItemConfig, MenuSection) provide type safety
  • Well-documented code with JSDoc comments explaining the menu structure and precedence rules
  • Semantic HTML with <button> elements for menu items improves over the previous implementation

Minor observations:

  • MainMenu.tsx:509 - Returning <></> for empty case works, but return null is more conventional for conditional rendering
  • The LOG constant at line 47 could use the module pattern getLogger("MainMenu") which it does - good practice

The styled components cleanup removes ~120 lines of unused code, which improves maintainability.

Test Coverage

Unit Tests (MainMenu.test.tsx):

  • Comprehensive coverage with 30+ test cases covering:
    • Menu item visibility and hiding based on configuration
    • Host menu items integration and conflict resolution
    • Developer mode features (Clear cache)
    • Disabled states when server disconnected
    • Keyboard shortcuts display
    • Menu item ordering
    • Recording state indicators
    • Metrics tracking
  • Good use of negative assertions (e.g., verifying items are NOT rendered when hidden)
  • Proper async handling with vi.useFakeTimers() for BaseWeb popover

E2E Tests:

  • Updated main_menu_test.py with new escape key close test (lines 30-39)
  • Updated st_set_page_config_test.py to use more robust text-based locators instead of index-based (lines 271-275, 287-309)
  • The e2e tests follow best practices:
    • Uses get_by_test_id and get_by_text as recommended
    • Uses expect assertions with auto-wait
    • Avoids index-based selectors

Gap: No unit test for the openInNewTab error handling when popup blocker prevents window.open (line 60-64). Consider adding a test that mocks window.open returning null.

Backwards Compatibility

User-visible change:

  • The label "Record a screencast" was changed to "Record screen" (line 247 in MainMenu.tsx)
  • This is reflected in e2e test updates (main_menu_test.py lines 118, 129)

This is a deliberate design change and is backwards compatible in terms of functionality - no API changes or breaking changes to st.set_page_config() menu items.

The menu item ordering remains consistent with the existing behavior, and all configurable menu items (getHelpUrl, reportABugUrl, aboutSectionMd, hideGetHelp, hideReportABug) continue to work as expected.

Security & Risk

No security concerns identified:

  • URL handling in openInNewTab uses window.open(url, "_blank") which is standard and safe
  • Host menu items are properly sanitized through the existing sendMessageToHost callback pattern
  • No new external dependencies introduced

Low risk items:

  • The refactoring is significant but well-tested
  • BaseWeb's StatefulPopover continues to provide focus lock and escape key handling

Accessibility

Current state:

  • Uses semantic <button> elements for menu items (good improvement)
  • Menu container has aria-label="Main menu" (line 440)
  • Dividers have role="separator" and aria-hidden="true" (lines 418-419)
  • Disabled state properly communicated via HTML disabled attribute

Concerns requiring follow-up (as noted in PR description):

  1. styled-components.ts:98-100 - The &:focus-visible style removes outline without providing an alternative focus indicator. The TODO comment acknowledges this, but it should be addressed promptly:
    "&:hover, &:focus-visible": {
      backgroundColor: theme.colors.darkenedBgMix15,
    },

    // TODO(accessibility): Add visible focus ring for keyboard navigation.
    "&:focus-visible": {
      outline: "none",
    },
  1. Missing ARIA menu pattern attributes (as noted in test comments, lines 619-620):
    • aria-haspopup, aria-expanded on trigger
    • role="menu", role="menuitem" on menu/items

These are acknowledged for a follow-up PR but should be tracked.

Recommendations

  1. Add focus ring styling - The TODO at line 98-99 in styled-components.ts should be addressed before or shortly after merge. Consider using theme.shadows.focusRing or similar for consistent focus indication.

  2. Consider adding unit test for popup blocker handling - The openInNewTab function logs a warning when popup is blocked (line 61-64), but this isn't tested. A simple test mocking window.open returning null would improve coverage.

  3. Track accessibility improvements - Ensure the mentioned ARIA attributes (aria-haspopup, aria-expanded, role="menu", role="menuitem") are addressed in the follow-up PR as planned.

  4. Minor cleanup - Line 509 could use return null instead of return <></> for slightly more idiomatic React code.

Verdict

APPROVED: This is a well-executed refactoring that improves code maintainability through a data-driven approach while maintaining backwards compatibility. The test coverage is comprehensive, and the acknowledged accessibility gaps are planned for a follow-up PR. The code follows React best practices and the Streamlit codebase conventions.


This is an automated AI review using opus-4.5-thinking. Please verify the feedback and use your judgment.

@mayagbarnes mayagbarnes marked this pull request as ready for review February 5, 2026 21:53
@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Feb 5, 2026

📉 Python coverage change detected

The Python unit test coverage has decreased by 0.0024%

  • Current PR: 93.1664% (23443 statements, 1602 missed)
  • Latest develop: 93.1688% (23466 statements, 1603 missed)

✅ Coverage change is within normal range.

Coverage by files
Name Stmts Miss Cover
streamlit/__init__.py 136 0 100%
streamlit/__main__.py 3 3 0%
streamlit/auth_util.py 231 25 89%
streamlit/cli_util.py 39 6 85%
streamlit/column_config.py 3 0 100%
streamlit/commands/__init__.py 0 0 100%
streamlit/commands/echo.py 54 2 96%
streamlit/commands/execution_control.py 70 10 86%
streamlit/commands/logo.py 53 1 98%
streamlit/commands/navigation.py 106 2 98%
streamlit/commands/page_config.py 106 4 96%
streamlit/components/__init__.py 0 0 100%
streamlit/components/lib/__init__.py 0 0 100%
streamlit/components/lib/local_component_registry.py 35 2 94%
streamlit/components/types/__init__.py 0 0 100%
streamlit/components/types/base_component_registry.py 14 0 100%
streamlit/components/types/base_custom_component.py 48 6 88%
streamlit/components/v1/__init__.py 5 0 100%
streamlit/components/v1/component_arrow.py 33 2 94%
streamlit/components/v1/component_registry.py 41 3 93%
streamlit/components/v1/components.py 4 4 0%
streamlit/components/v1/custom_component.py 84 7 92%
streamlit/components/v2/__init__.py 27 0 100%
streamlit/components/v2/bidi_component/__init__.py 4 0 100%
streamlit/components/v2/bidi_component/constants.py 5 0 100%
streamlit/components/v2/bidi_component/main.py 148 17 89%
streamlit/components/v2/bidi_component/serialization.py 81 2 98%
streamlit/components/v2/bidi_component/state.py 13 0 100%
streamlit/components/v2/component_definition_resolver.py 30 0 100%
streamlit/components/v2/component_file_watcher.py 117 9 92%
streamlit/components/v2/component_manager.py 97 13 87%
streamlit/components/v2/component_manifest_handler.py 24 0 100%
streamlit/components/v2/component_path_utils.py 65 5 92%
streamlit/components/v2/component_registry.py 121 8 93%
streamlit/components/v2/get_bidi_component_manager.py 8 1 88%
streamlit/components/v2/manifest_scanner.py 227 25 89%
streamlit/components/v2/presentation.py 84 19 77%
streamlit/components/v2/types.py 8 8 0%
streamlit/config.py 417 12 97%
streamlit/config_option.py 79 3 96%
streamlit/config_util.py 308 7 98%
streamlit/connections/__init__.py 6 0 100%
streamlit/connections/base_connection.py 49 0 100%
streamlit/connections/snowflake_connection.py 98 16 84%
streamlit/connections/snowpark_connection.py 44 3 93%
streamlit/connections/sql_connection.py 56 6 89%
streamlit/connections/util.py 33 0 100%
streamlit/cursor.py 130 1 99%
streamlit/dataframe_util.py 506 47 91%
streamlit/delta_generator.py 251 7 97%
streamlit/delta_generator_singletons.py 74 7 91%
streamlit/deprecation_util.py 66 4 94%
streamlit/development.py 1 0 100%
streamlit/elements/__init__.py 0 0 100%
streamlit/elements/alert.py 60 0 100%
streamlit/elements/arrow.py 210 18 91%
streamlit/elements/balloons.py 10 0 100%
streamlit/elements/bokeh_chart.py 9 0 100%
streamlit/elements/code.py 20 1 95%
streamlit/elements/deck_gl_json_chart.py 104 7 93%
streamlit/elements/dialog_decorator.py 38 0 100%
streamlit/elements/doc_string.py 227 9 96%
streamlit/elements/empty.py 16 4 75%
streamlit/elements/exception.py 101 10 90%
streamlit/elements/form.py 56 2 96%
streamlit/elements/graphviz_chart.py 36 1 97%
streamlit/elements/heading.py 56 0 100%
streamlit/elements/html.py 49 0 100%
streamlit/elements/iframe.py 29 0 100%
streamlit/elements/image.py 32 0 100%
streamlit/elements/json.py 48 6 88%
streamlit/elements/layouts.py 140 3 98%
streamlit/elements/lib/__init__.py 0 0 100%
streamlit/elements/lib/built_in_chart_utils.py 403 30 93%
streamlit/elements/lib/color_util.py 103 4 96%
streamlit/elements/lib/column_config_utils.py 169 1 99%
streamlit/elements/lib/column_types.py 190 4 98%
streamlit/elements/lib/dialog.py 69 1 99%
streamlit/elements/lib/dicttools.py 39 2 95%
streamlit/elements/lib/file_uploader_utils.py 30 0 100%
streamlit/elements/lib/form_utils.py 26 0 100%
streamlit/elements/lib/image_utils.py 176 21 88%
streamlit/elements/lib/js_number.py 28 3 89%
streamlit/elements/lib/layout_utils.py 121 1 99%
streamlit/elements/lib/mutable_status_container.py 73 4 95%
streamlit/elements/lib/options_selector_utils.py 142 2 99%
streamlit/elements/lib/pandas_styler_utils.py 80 2 98%
streamlit/elements/lib/policies.py 56 1 98%
streamlit/elements/lib/shortcut_utils.py 42 2 95%
streamlit/elements/lib/streamlit_plotly_theme.py 48 0 100%
streamlit/elements/lib/subtitle_utils.py 76 5 93%
streamlit/elements/lib/utils.py 76 5 93%
streamlit/elements/map.py 110 1 99%
streamlit/elements/markdown.py 64 2 97%
streamlit/elements/media.py 181 8 96%
streamlit/elements/metric.py 104 0 100%
streamlit/elements/pdf.py 49 2 96%
streamlit/elements/plotly_chart.py 129 6 95%
streamlit/elements/progress.py 36 0 100%
streamlit/elements/pyplot.py 39 2 95%
streamlit/elements/snow.py 10 0 100%
streamlit/elements/space.py 12 0 100%
streamlit/elements/spinner.py 44 3 93%
streamlit/elements/text.py 16 0 100%
streamlit/elements/toast.py 26 0 100%
streamlit/elements/vega_charts.py 244 5 98%
streamlit/elements/widgets/__init__.py 0 0 100%
streamlit/elements/widgets/audio_input.py 68 1 99%
streamlit/elements/widgets/button.py 245 6 98%
streamlit/elements/widgets/button_group.py 207 15 93%
streamlit/elements/widgets/camera_input.py 62 1 98%
streamlit/elements/widgets/chat.py 237 38 84%
streamlit/elements/widgets/checkbox.py 52 0 100%
streamlit/elements/widgets/color_picker.py 59 2 97%
streamlit/elements/widgets/data_editor.py 254 14 94%
streamlit/elements/widgets/feedback.py 69 0 100%
streamlit/elements/widgets/file_uploader.py 108 10 91%
streamlit/elements/widgets/multiselect.py 114 5 96%
streamlit/elements/widgets/number_input.py 146 4 97%
streamlit/elements/widgets/radio.py 103 5 95%
streamlit/elements/widgets/select_slider.py 122 3 98%
streamlit/elements/widgets/selectbox.py 97 3 97%
streamlit/elements/widgets/slider.py 241 8 97%
streamlit/elements/widgets/text_widgets.py 130 6 95%
streamlit/elements/widgets/time_widgets.py 425 21 95%
streamlit/elements/write.py 166 20 88%
streamlit/emojis.py 4 0 100%
streamlit/env_util.py 21 3 86%
streamlit/error_util.py 33 2 94%
streamlit/errors.py 184 25 86%
streamlit/external/__init__.py 0 0 100%
streamlit/external/langchain/__init__.py 2 0 100%
streamlit/external/langchain/streamlit_callback_handler.py 141 82 42%
streamlit/file_util.py 84 8 90%
streamlit/git_util.py 100 5 95%
streamlit/logger.py 54 0 100%
streamlit/material_icon_names.py 1 0 100%
streamlit/navigation/__init__.py 0 0 100%
streamlit/navigation/page.py 78 2 97%
streamlit/net_util.py 55 3 95%
streamlit/path_security.py 17 1 94%
streamlit/platform.py 10 1 90%
streamlit/runtime/__init__.py 8 0 100%
streamlit/runtime/app_session.py 469 85 82%
streamlit/runtime/caching/__init__.py 21 0 100%
streamlit/runtime/caching/cache_data_api.py 191 3 98%
streamlit/runtime/caching/cache_errors.py 44 4 91%
streamlit/runtime/caching/cache_resource_api.py 165 1 99%
streamlit/runtime/caching/cache_type.py 11 1 91%
streamlit/runtime/caching/cache_utils.py 176 9 95%
streamlit/runtime/caching/cached_message_replay.py 108 1 99%
streamlit/runtime/caching/hashing.py 310 25 92%
streamlit/runtime/caching/legacy_cache_api.py 14 0 100%
streamlit/runtime/caching/storage/__init__.py 2 0 100%
streamlit/runtime/caching/storage/cache_storage_protocol.py 29 0 100%
streamlit/runtime/caching/storage/dummy_cache_storage.py 21 0 100%
streamlit/runtime/caching/storage/in_memory_cache_storage_wrapper.py 67 1 99%
streamlit/runtime/caching/storage/local_disk_cache_storage.py 86 4 95%
streamlit/runtime/caching/ttl_cleanup_cache.py 28 0 100%
streamlit/runtime/connection_factory.py 96 11 89%
streamlit/runtime/context.py 137 0 100%
streamlit/runtime/context_util.py 18 0 100%
streamlit/runtime/credentials.py 139 4 97%
streamlit/runtime/download_data_util.py 27 0 100%
streamlit/runtime/forward_msg_cache.py 23 2 91%
streamlit/runtime/forward_msg_queue.py 63 4 94%
streamlit/runtime/fragment.py 112 2 98%
streamlit/runtime/media_file_manager.py 110 7 94%
streamlit/runtime/media_file_storage.py 15 0 100%
streamlit/runtime/memory_media_file_storage.py 73 0 100%
streamlit/runtime/memory_session_storage.py 15 0 100%
streamlit/runtime/memory_uploaded_file_manager.py 46 1 98%
streamlit/runtime/metrics_util.py 195 13 93%
streamlit/runtime/pages_manager.py 59 2 97%
streamlit/runtime/runtime.py 252 16 94%
streamlit/runtime/runtime_util.py 30 1 97%
streamlit/runtime/script_data.py 16 0 100%
streamlit/runtime/scriptrunner/__init__.py 5 0 100%
streamlit/runtime/scriptrunner/exec_code.py 49 5 90%
streamlit/runtime/scriptrunner/magic.py 83 1 99%
streamlit/runtime/scriptrunner/magic_funcs.py 10 1 90%
streamlit/runtime/scriptrunner/script_cache.py 27 0 100%
streamlit/runtime/scriptrunner/script_runner.py 235 27 89%
streamlit/runtime/scriptrunner_utils/__init__.py 0 0 100%
streamlit/runtime/scriptrunner_utils/exceptions.py 9 1 89%
streamlit/runtime/scriptrunner_utils/script_requests.py 106 5 95%
streamlit/runtime/scriptrunner_utils/script_run_context.py 118 0 100%
streamlit/runtime/secrets.py 241 25 90%
streamlit/runtime/session_manager.py 71 2 97%
streamlit/runtime/state/__init__.py 7 0 100%
streamlit/runtime/state/common.py 55 1 98%
streamlit/runtime/state/presentation.py 19 4 79%
streamlit/runtime/state/query_params.py 274 6 98%
streamlit/runtime/state/query_params_proxy.py 71 0 100%
streamlit/runtime/state/safe_session_state.py 77 9 88%
streamlit/runtime/state/session_state.py 503 36 93%
streamlit/runtime/state/session_state_proxy.py 62 8 87%
streamlit/runtime/state/widgets.py 19 0 100%
streamlit/runtime/stats.py 132 4 97%
streamlit/runtime/theme_util.py 46 1 98%
streamlit/runtime/uploaded_file_manager.py 39 3 92%
streamlit/runtime/websocket_session_manager.py 116 0 100%
streamlit/source_util.py 36 1 97%
streamlit/starlette.py 2 0 100%
streamlit/string_util.py 93 9 90%
streamlit/temporary_directory.py 18 1 94%
streamlit/testing/__init__.py 0 0 100%
streamlit/testing/v1/__init__.py 2 0 100%
streamlit/testing/v1/app_test.py 245 4 98%
streamlit/testing/v1/element_tree.py 1426 58 96%
streamlit/testing/v1/local_script_runner.py 71 2 97%
streamlit/testing/v1/util.py 17 0 100%
streamlit/time_util.py 28 1 96%
streamlit/type_util.py 148 16 89%
streamlit/url_util.py 39 4 90%
streamlit/user_info.py 105 8 92%
streamlit/util.py 38 1 97%
streamlit/version.py 3 0 100%
streamlit/watcher/__init__.py 3 0 100%
streamlit/watcher/event_based_path_watcher.py 184 25 86%
streamlit/watcher/folder_black_list.py 14 1 93%
streamlit/watcher/local_sources_watcher.py 127 9 93%
streamlit/watcher/path_watcher.py 42 3 93%
streamlit/watcher/polling_path_watcher.py 55 2 96%
streamlit/watcher/util.py 59 1 98%
streamlit/web/__init__.py 0 0 100%
streamlit/web/bootstrap.py 174 21 88%
streamlit/web/cache_storage_manager_config.py 5 0 100%
streamlit/web/cli.py 188 16 91%
streamlit/web/server/__init__.py 5 0 100%
streamlit/web/server/app_discovery.py 104 5 95%
streamlit/web/server/app_static_file_handler.py 35 3 91%
streamlit/web/server/authlib_tornado_integration.py 42 5 88%
streamlit/web/server/bidi_component_request_handler.py 65 8 88%
streamlit/web/server/browser_websocket_handler.py 147 20 86%
streamlit/web/server/component_file_utils.py 27 0 100%
streamlit/web/server/component_request_handler.py 55 4 93%
streamlit/web/server/media_file_handler.py 65 9 86%
streamlit/web/server/oauth_authlib_routes.py 162 35 78%
streamlit/web/server/oidc_mixin.py 46 0 100%
streamlit/web/server/routes.py 95 10 89%
streamlit/web/server/server.py 195 13 93%
streamlit/web/server/server_util.py 72 5 93%
streamlit/web/server/starlette/__init__.py 3 0 100%
streamlit/web/server/starlette/starlette_app.py 148 4 97%
streamlit/web/server/starlette/starlette_app_utils.py 101 7 93%
streamlit/web/server/starlette/starlette_auth_routes.py 233 51 78%
streamlit/web/server/starlette/starlette_gzip_middleware.py 30 0 100%
streamlit/web/server/starlette/starlette_path_security_middleware.py 22 0 100%
streamlit/web/server/starlette/starlette_routes.py 346 86 75%
streamlit/web/server/starlette/starlette_server.py 167 7 96%
streamlit/web/server/starlette/starlette_server_config.py 13 0 100%
streamlit/web/server/starlette/starlette_static_routes.py 70 6 91%
streamlit/web/server/starlette/starlette_websocket.py 203 23 89%
streamlit/web/server/stats_request_handler.py 59 5 92%
streamlit/web/server/upload_file_request_handler.py 59 7 88%
streamlit/web/server/websocket_headers.py 19 1 95%
TOTAL 23443 1602 93%

📊 View detailed coverage comparison

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Feb 5, 2026

📉 Frontend coverage change detected

The frontend unit test (vitest) coverage has decreased by 0.1500%

  • Current PR: 86.5400% (13853 lines, 1864 missed)
  • Latest develop: 86.6900% (13909 lines, 1850 missed)

💡 Consider adding more unit tests to maintain or improve coverage.

📊 View detailed coverage comparison

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Feb 9, 2026

Summary

This PR refactors the main menu into a data-driven, sectioned structure with new styled button rows, updates the "Record screen" label, adds a menu width theme primitive, and refreshes e2e snapshots. It also updates frontend and e2e tests, including a new Escape-to-close test.

Code Quality

Overall structure is cleaner: menu data generation is separated from rendering, and the menu layout is easier to reason about and extend. The new helper utilities and tests read well and align with existing patterns.

Test Coverage

Frontend unit tests were updated for the refactored menu (including minimal mode and host items). E2E coverage includes updated snapshots and a new Escape-to-close test for the menu. No Python unit tests were required for these changes.

Backwards Compatibility

No public API or protobuf changes detected. This is primarily a UI refactor with updated menu labels and layout, which should be backwards compatible for users.

Security & Risk

No new security-sensitive flows identified in this change set.

Accessibility

The menu item buttons remove visible focus outlines without replacing them, which breaks keyboard focus visibility and violates our a11y guidelines. StyledMenuItemRow currently sets outline: none for :focus-visible without adding a replacement ring. See frontend/app/src/components/MainMenu/styled-components.ts:94-101.

Recommendations

  1. Add a visible focus ring for StyledMenuItemRow when :focus-visible (use theme.shadows or similar) and remove/replace the outline: none rule. File: frontend/app/src/components/MainMenu/styled-components.ts:94-101.
  2. If host-provided separators are intended to create grouping, consider mapping host separator items into sections instead of dropping them. File: frontend/app/src/components/MainMenu/MainMenu.tsx:301-306.

Verdict

CHANGES_REQUESTED: Add a visible focus style for menu item buttons to meet accessibility requirements.


This is an automated AI review using gpt-5.2-codex-high. Please verify the feedback and use your judgment.

@github-actions github-actions bot added the do-not-merge PR is blocked from merging label Feb 9, 2026
@lukasmasuch lukasmasuch removed the do-not-merge PR is blocked from merging label Feb 9, 2026
Copy link
Copy Markdown
Collaborator

@lukasmasuch lukasmasuch left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM 👍

// Host menu items - injected by host (e.g., Streamlit Cloud)
// Some host items are hidden if developer settings conflict
for (const hostItem of hostMenuItems) {
if (hostItem.type === "separator") continue
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

question: The codex review complains that the new code just ignores the separators provided by host. Is this intended? Maybe add a comment here.

@mayagbarnes mayagbarnes merged commit 9c2792f into feature/new-app-menu Feb 9, 2026
39 checks passed
@mayagbarnes mayagbarnes deleted the core-menu-refactor branch February 9, 2026 18:18
sfc-gh-mbarnes pushed a commit that referenced this pull request Feb 9, 2026
Core refactor of the `MainMenu` component that replaces BaseWeb's `StatefulMenu` with a custom data-driven implementation. This is the foundational PR for the `MainMenu` redesign, going into the feature branch for incremental review.

- Remove `StatefulMenu` from BaseWeb, keep `StatefulPopover` for positioning/focus lock
- Introduce section-based data model (`MenuSection`, `MenuItemConfig`)
- Pure functions for menu construction (`buildMenuData`,`buildMenuSections`, `buildCommonItems`, etc.)
- Memoized `MenuContent` component with stable callbacks
sfc-gh-mbarnes pushed a commit that referenced this pull request Feb 12, 2026
Core refactor of the `MainMenu` component that replaces BaseWeb's `StatefulMenu` with a custom data-driven implementation. This is the foundational PR for the `MainMenu` redesign, going into the feature branch for incremental review.

- Remove `StatefulMenu` from BaseWeb, keep `StatefulPopover` for positioning/focus lock
- Introduce section-based data model (`MenuSection`, `MenuItemConfig`)
- Pure functions for menu construction (`buildMenuData`,`buildMenuSections`, `buildCommonItems`, etc.)
- Memoized `MenuContent` component with stable callbacks
sfc-gh-mbarnes pushed a commit that referenced this pull request Feb 19, 2026
Core refactor of the `MainMenu` component that replaces BaseWeb's `StatefulMenu` with a custom data-driven implementation. This is the foundational PR for the `MainMenu` redesign, going into the feature branch for incremental review.

- Remove `StatefulMenu` from BaseWeb, keep `StatefulPopover` for positioning/focus lock
- Introduce section-based data model (`MenuSection`, `MenuItemConfig`)
- Pure functions for menu construction (`buildMenuData`,`buildMenuSections`, `buildCommonItems`, etc.)
- Memoized `MenuContent` component with stable callbacks
sfc-gh-mbarnes pushed a commit that referenced this pull request Feb 24, 2026
Core refactor of the `MainMenu` component that replaces BaseWeb's `StatefulMenu` with a custom data-driven implementation. This is the foundational PR for the `MainMenu` redesign, going into the feature branch for incremental review.

- Remove `StatefulMenu` from BaseWeb, keep `StatefulPopover` for positioning/focus lock
- Introduce section-based data model (`MenuSection`, `MenuItemConfig`)
- Pure functions for menu construction (`buildMenuData`,`buildMenuSections`, `buildCommonItems`, etc.)
- Memoized `MenuContent` component with stable callbacks
mayagbarnes added a commit that referenced this pull request Feb 24, 2026
This PR is the culmination of the following PRs approved into this feature branch:
- [Core `MainMenu` Refactor](#13833)
- [`MainMenu` accessibility improvements](#13878)
- [Add theme switcher to `MainMenu`](#13937)
- [Add auto-rerun toggle to `MainMenu`](#13988)
- [Add version footer to `MainMenu`](#14028)
- [Remove `SettingsDialog`](#14048)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

change:feature PR contains new feature or enhancement implementation impact:users PR changes affect end users

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants