Skip to content

Conversation

@kmcgrady
Copy link
Collaborator

@kmcgrady kmcgrady commented Oct 18, 2025

Describe your changes

This change introduces the Visitor Design Pattern to our App Node hierarchy. This allows having one file represent an operation across all of the node types rather having to edit every node file (and lose context).

To keep the PR small and not touch other methods, I created a DebugVisitor that is designed to print out the node hierarchy (which I think will be useful in debugging). It also demonstrates the purpose of the design pattern.

Testing Plan

  • Unit Tests (JS and/or Python) - Adds JS unit tests for the Debug Visitor and the AppRoot.

Contribution License Agreement

By submitting this pull request you agree that all contributions to this project are made under the Apache 2.0 license.

@snyk-io
Copy link
Contributor

snyk-io bot commented Oct 18, 2025

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

Status Scanner Critical High Medium Low Total (0)
Licenses 0 0 0 0 0 issues
Open Source Security 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
Contributor

github-actions bot commented Oct 18, 2025

✅ PR preview is ready!

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

@kmcgrady kmcgrady added change:refactor PR contains code refactoring without behavior change security-assessment-completed Security assessment has been completed for PR impact:internal PR changes only affect internal code labels Oct 18, 2025
@kmcgrady kmcgrady requested a review from Copilot October 18, 2025 20:54
Copy link
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 introduces the Visitor design pattern to the AppNode hierarchy, enabling operations across different node types to be defined in a single location rather than scattered across node files. A DebugVisitor implementation is provided as a demonstration that generates tree-like debug representations of the node hierarchy.

Key changes:

  • Added AppNodeVisitor<T> interface defining visit methods for BlockNode and ElementNode
  • Implemented DebugVisitor that generates formatted debug output showing node structure, types, and metadata
  • Extended AppNode interface and implementations with accept() and debug() methods

Reviewed Changes

Copilot reviewed 10 out of 10 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
frontend/lib/src/render-tree/visitors/AppNodeVisitor.interface.ts Defines the visitor interface with visit methods for each node type
frontend/lib/src/render-tree/visitors/DebugVisitor.ts Implements a visitor that generates tree-structured debug output
frontend/lib/src/render-tree/visitors/DebugVisitor.test.ts Tests for DebugVisitor covering various node configurations and output formatting
frontend/lib/src/render-tree/AppNode.interface.ts Adds accept() and debug() method signatures to the AppNode interface
frontend/lib/src/render-tree/ElementNode.ts Implements visitor pattern methods on ElementNode
frontend/lib/src/render-tree/ElementNode.test.ts Tests for ElementNode visitor functionality
frontend/lib/src/render-tree/BlockNode.ts Implements visitor pattern methods on BlockNode
frontend/lib/src/render-tree/BlockNode.test.ts Tests for BlockNode visitor functionality
frontend/lib/src/render-tree/AppRoot.ts Adds debug method to AppRoot that labels and formats child sections
frontend/lib/src/render-tree/AppRoot.test.ts Tests AppRoot debug output format

@github-actions
Copy link
Contributor

github-actions bot commented Oct 18, 2025

📈 Frontend coverage change detected

The frontend unit test (vitest) coverage has increased by 0.0200%

  • Current PR: 86.0300% (49636 lines, 6932 missed)
  • Latest develop: 86.0100% (49525 lines, 6927 missed)

✅ Coverage change is within normal range.

📊 View detailed coverage comparison

@kmcgrady kmcgrady force-pushed the 10-18-add_visitor_pattern_to_appnodes_with_a_debug_visitor branch from 710a7b8 to 5a5634d Compare October 18, 2025 21:41
@kmcgrady kmcgrady requested a review from Copilot October 18, 2025 21:52
Copy link
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

Copilot reviewed 10 out of 10 changed files in this pull request and generated no new comments.

@kmcgrady kmcgrady force-pushed the 10-18-add_visitor_pattern_to_appnodes_with_a_debug_visitor branch from 5a5634d to f300282 Compare October 19, 2025 04:21
@kmcgrady kmcgrady force-pushed the 10-18-break_up_appnode_into_separate_files branch from fd9ced5 to 0004f15 Compare October 19, 2025 04:21
@kmcgrady kmcgrady force-pushed the 10-18-add_visitor_pattern_to_appnodes_with_a_debug_visitor branch from f300282 to f9e4e45 Compare October 19, 2025 16:20
@kmcgrady kmcgrady force-pushed the 10-18-break_up_appnode_into_separate_files branch from 0004f15 to da3475c Compare October 19, 2025 16:20
@kmcgrady kmcgrady force-pushed the 10-18-add_visitor_pattern_to_appnodes_with_a_debug_visitor branch from f9e4e45 to f4db9e6 Compare October 19, 2025 16:47
@kmcgrady kmcgrady force-pushed the 10-18-break_up_appnode_into_separate_files branch from da3475c to 7627ccb Compare October 19, 2025 16:47
@kmcgrady kmcgrady force-pushed the 10-18-break_up_appnode_into_separate_files branch from 7627ccb to f5f6301 Compare October 24, 2025 15:05
@kmcgrady kmcgrady force-pushed the 10-18-add_visitor_pattern_to_appnodes_with_a_debug_visitor branch 2 times, most recently from 9ee7726 to 8c7d056 Compare October 24, 2025 16:42
@kmcgrady kmcgrady force-pushed the 10-18-break_up_appnode_into_separate_files branch from f5f6301 to 17e8ba1 Compare October 24, 2025 16:42
@kmcgrady kmcgrady force-pushed the 10-18-add_visitor_pattern_to_appnodes_with_a_debug_visitor branch from 8c7d056 to 8887654 Compare October 24, 2025 16:56
@kmcgrady kmcgrady force-pushed the 10-18-break_up_appnode_into_separate_files branch from 17e8ba1 to 19b19a3 Compare October 24, 2025 16:56
@github-actions
Copy link
Contributor

github-actions bot commented Oct 24, 2025

📉 Python coverage change detected

The Python unit test coverage has decreased by 0.0000%

  • Current PR: 92.8205% (20057 statements, 1440 missed)
  • Latest develop: 92.8205% (20057 statements, 1440 missed)

✅ Coverage change is within normal range.

Coverage by files
Name Stmts Miss Cover
streamlit/__init__.py 141 0 100%
streamlit/__main__.py 3 3 0%
streamlit/auth_util.py 100 25 75%
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 11 80%
streamlit/commands/execution_control.py 59 10 83%
streamlit/commands/experimental_query_params.py 40 2 95%
streamlit/commands/logo.py 41 6 85%
streamlit/commands/navigation.py 106 2 98%
streamlit/commands/page_config.py 101 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 49 6 88%
streamlit/components/v1/__init__.py 5 0 100%
streamlit/components/v1/component_arrow.py 33 8 76%
streamlit/components/v1/component_registry.py 41 3 93%
streamlit/components/v1/components.py 4 4 0%
streamlit/components/v1/custom_component.py 85 7 92%
streamlit/components/v2/__init__.py 24 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 109 12 89%
streamlit/components/v2/bidi_component/serialization.py 81 5 94%
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 96 16 83%
streamlit/components/v2/component_manifest_handler.py 24 0 100%
streamlit/components/v2/component_path_utils.py 68 5 93%
streamlit/components/v2/component_registry.py 118 8 93%
streamlit/components/v2/get_bidi_component_manager.py 8 1 88%
streamlit/components/v2/manifest_scanner.py 224 25 89%
streamlit/components/v2/presentation.py 84 19 77%
streamlit/components/v2/types.py 8 8 0%
streamlit/config.py 412 12 97%
streamlit/config_option.py 79 3 96%
streamlit/config_util.py 288 7 98%
streamlit/connections/__init__.py 6 0 100%
streamlit/connections/base_connection.py 45 0 100%
streamlit/connections/snowflake_connection.py 60 13 78%
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 82 2 98%
streamlit/dataframe_util.py 501 45 91%
streamlit/delta_generator.py 205 6 97%
streamlit/delta_generator_singletons.py 76 8 89%
streamlit/deprecation_util.py 59 4 93%
streamlit/development.py 1 0 100%
streamlit/elements/__init__.py 0 0 100%
streamlit/elements/alert.py 60 0 100%
streamlit/elements/arrow.py 198 15 92%
streamlit/elements/balloons.py 10 0 100%
streamlit/elements/bokeh_chart.py 27 0 100%
streamlit/elements/code.py 20 1 95%
streamlit/elements/deck_gl_json_chart.py 104 10 90%
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 54 2 96%
streamlit/elements/graphviz_chart.py 35 1 97%
streamlit/elements/heading.py 56 0 100%
streamlit/elements/html.py 48 0 100%
streamlit/elements/iframe.py 29 0 100%
streamlit/elements/image.py 32 0 100%
streamlit/elements/json.py 39 2 95%
streamlit/elements/layouts.py 140 3 98%
streamlit/elements/lib/__init__.py 0 0 100%
streamlit/elements/lib/built_in_chart_utils.py 387 26 93%
streamlit/elements/lib/color_util.py 100 4 96%
streamlit/elements/lib/column_config_utils.py 168 1 99%
streamlit/elements/lib/column_types.py 189 4 98%
streamlit/elements/lib/dialog.py 67 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 109 1 99%
streamlit/elements/lib/mutable_status_container.py 73 4 95%
streamlit/elements/lib/options_selector_utils.py 90 0 100%
streamlit/elements/lib/pandas_styler_utils.py 80 2 98%
streamlit/elements/lib/policies.py 56 1 98%
streamlit/elements/lib/streamlit_plotly_theme.py 49 0 100%
streamlit/elements/lib/subtitle_utils.py 76 13 83%
streamlit/elements/lib/utils.py 76 5 93%
streamlit/elements/map.py 110 1 99%
streamlit/elements/markdown.py 63 2 97%
streamlit/elements/media.py 181 8 96%
streamlit/elements/metric.py 91 0 100%
streamlit/elements/pdf.py 49 2 96%
streamlit/elements/plotly_chart.py 114 4 96%
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 34 0 100%
streamlit/elements/text.py 16 0 100%
streamlit/elements/toast.py 26 0 100%
streamlit/elements/vega_charts.py 226 3 99%
streamlit/elements/widgets/__init__.py 0 0 100%
streamlit/elements/widgets/audio_input.py 68 10 85%
streamlit/elements/widgets/button.py 214 3 99%
streamlit/elements/widgets/button_group.py 161 1 99%
streamlit/elements/widgets/camera_input.py 62 10 84%
streamlit/elements/widgets/chat.py 169 40 76%
streamlit/elements/widgets/checkbox.py 52 0 100%
streamlit/elements/widgets/color_picker.py 56 2 96%
streamlit/elements/widgets/data_editor.py 239 14 94%
streamlit/elements/widgets/file_uploader.py 103 18 83%
streamlit/elements/widgets/multiselect.py 105 4 96%
streamlit/elements/widgets/number_input.py 143 5 97%
streamlit/elements/widgets/radio.py 83 5 94%
streamlit/elements/widgets/select_slider.py 97 0 100%
streamlit/elements/widgets/selectbox.py 91 2 98%
streamlit/elements/widgets/slider.py 241 8 97%
streamlit/elements/widgets/text_widgets.py 130 6 95%
streamlit/elements/widgets/time_widgets.py 248 15 94%
streamlit/elements/write.py 166 30 82%
streamlit/emojis.py 4 0 100%
streamlit/env_util.py 21 3 86%
streamlit/error_util.py 33 2 94%
streamlit/errors.py 188 25 87%
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/platform.py 10 1 90%
streamlit/runtime/__init__.py 8 0 100%
streamlit/runtime/app_session.py 447 94 79%
streamlit/runtime/caching/__init__.py 19 0 100%
streamlit/runtime/caching/cache_data_api.py 164 3 98%
streamlit/runtime/caching/cache_errors.py 45 4 91%
streamlit/runtime/caching/cache_resource_api.py 122 0 100%
streamlit/runtime/caching/cache_type.py 11 1 91%
streamlit/runtime/caching/cache_utils.py 165 9 95%
streamlit/runtime/caching/cached_message_replay.py 108 1 99%
streamlit/runtime/caching/hashing.py 311 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 31 2 94%
streamlit/runtime/caching/storage/dummy_cache_storage.py 21 0 100%
streamlit/runtime/caching/storage/in_memory_cache_storage_wrapper.py 60 0 100%
streamlit/runtime/caching/storage/local_disk_cache_storage.py 86 4 95%
streamlit/runtime/connection_factory.py 85 9 89%
streamlit/runtime/context.py 140 0 100%
streamlit/runtime/context_util.py 18 0 100%
streamlit/runtime/credentials.py 139 4 97%
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 69 7 90%
streamlit/runtime/media_file_storage.py 15 0 100%
streamlit/runtime/memory_media_file_storage.py 68 0 100%
streamlit/runtime/memory_session_storage.py 15 0 100%
streamlit/runtime/memory_uploaded_file_manager.py 41 1 98%
streamlit/runtime/metrics_util.py 193 12 94%
streamlit/runtime/pages_manager.py 59 2 97%
streamlit/runtime/runtime.py 248 18 93%
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 230 27 88%
streamlit/runtime/scriptrunner_utils/__init__.py 0 0 100%
streamlit/runtime/scriptrunner_utils/exceptions.py 11 1 91%
streamlit/runtime/scriptrunner_utils/script_requests.py 106 5 95%
streamlit/runtime/scriptrunner_utils/script_run_context.py 135 2 99%
streamlit/runtime/secrets.py 242 26 89%
streamlit/runtime/session_manager.py 60 1 98%
streamlit/runtime/state/__init__.py 7 0 100%
streamlit/runtime/state/common.py 52 2 96%
streamlit/runtime/state/presentation.py 19 4 79%
streamlit/runtime/state/query_params.py 110 3 97%
streamlit/runtime/state/query_params_proxy.py 71 0 100%
streamlit/runtime/state/safe_session_state.py 77 11 86%
streamlit/runtime/state/session_state.py 433 29 93%
streamlit/runtime/state/session_state_proxy.py 62 8 87%
streamlit/runtime/state/widgets.py 15 1 93%
streamlit/runtime/stats.py 42 0 100%
streamlit/runtime/theme_util.py 46 1 98%
streamlit/runtime/uploaded_file_manager.py 39 3 92%
streamlit/runtime/websocket_session_manager.py 66 0 100%
streamlit/source_util.py 36 1 97%
streamlit/string_util.py 88 7 92%
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 239 6 97%
streamlit/testing/v1/element_tree.py 1327 87 93%
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 138 12 91%
streamlit/url_util.py 39 5 87%
streamlit/user_info.py 87 8 91%
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 181 24 87%
streamlit/watcher/folder_black_list.py 14 1 93%
streamlit/watcher/local_sources_watcher.py 127 9 93%
streamlit/watcher/path_watcher.py 43 3 93%
streamlit/watcher/polling_path_watcher.py 55 2 96%
streamlit/watcher/util.py 49 1 98%
streamlit/web/__init__.py 0 0 100%
streamlit/web/bootstrap.py 138 18 87%
streamlit/web/cache_storage_manager_config.py 5 0 100%
streamlit/web/cli.py 186 17 91%
streamlit/web/server/__init__.py 5 0 100%
streamlit/web/server/app_static_file_handler.py 29 3 90%
streamlit/web/server/authlib_tornado_integration.py 18 1 94%
streamlit/web/server/bidi_component_request_handler.py 65 8 88%
streamlit/web/server/browser_websocket_handler.py 115 31 73%
streamlit/web/server/component_file_utils.py 24 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 118 18 85%
streamlit/web/server/oidc_mixin.py 44 0 100%
streamlit/web/server/routes.py 90 7 92%
streamlit/web/server/server.py 188 11 94%
streamlit/web/server/server_util.py 67 5 93%
streamlit/web/server/stats_request_handler.py 53 4 92%
streamlit/web/server/upload_file_request_handler.py 53 9 83%
streamlit/web/server/websocket_headers.py 19 1 95%
TOTAL 20057 1440 93%

📊 View detailed coverage comparison

@kmcgrady kmcgrady force-pushed the 10-18-add_visitor_pattern_to_appnodes_with_a_debug_visitor branch from 8887654 to 09fe9dc Compare October 24, 2025 17:47
@kmcgrady kmcgrady marked this pull request as ready for review October 24, 2025 17:51
@kmcgrady kmcgrady force-pushed the 10-18-add_visitor_pattern_to_appnodes_with_a_debug_visitor branch from 09fe9dc to 53dcbe4 Compare October 24, 2025 18:40
@kmcgrady kmcgrady force-pushed the 10-18-add_visitor_pattern_to_appnodes_with_a_debug_visitor branch 3 times, most recently from acbcb79 to 3e9204c Compare October 24, 2025 18:55
expect(result).toBeUndefined()
})

it("can return a modified BlockNode through visitor", () => {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Can you help me understand this scenario a little better?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

This seems like an overzealous AI attempt. The beauty of visitors is that they can effectively return anything, and this test is effectively testing that undefined is an option. But this is probably too much, so I will just remove.

Copy link
Collaborator

@mayagbarnes mayagbarnes left a comment

Choose a reason for hiding this comment

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

LGTM

const runId = "1234567890abcdef"
const b = block([text("c")], runId)
const out = b.accept(new DebugVisitor())
expect(out.split("\n")[0]).toContain("(run: 123456)")
Copy link
Contributor

Choose a reason for hiding this comment

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

nit (non-blocking): You could make this slightly more stable by utilizing the MAX_HASH_LENGTH to create the expected value.

Copy link
Contributor

@sfc-gh-nbellante sfc-gh-nbellante left a comment

Choose a reason for hiding this comment

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

LGTM 👍

@kmcgrady kmcgrady force-pushed the 10-18-break_up_appnode_into_separate_files branch from 19b19a3 to d469706 Compare October 28, 2025 23:26
@kmcgrady kmcgrady force-pushed the 10-18-add_visitor_pattern_to_appnodes_with_a_debug_visitor branch from 3e9204c to 3c19282 Compare October 28, 2025 23:26
Base automatically changed from 10-18-break_up_appnode_into_separate_files to develop October 29, 2025 00:16
@kmcgrady kmcgrady force-pushed the 10-18-add_visitor_pattern_to_appnodes_with_a_debug_visitor branch from 3c19282 to fd5a951 Compare October 29, 2025 00:18
@kmcgrady kmcgrady enabled auto-merge (squash) October 29, 2025 00:39
@kmcgrady kmcgrady merged commit a60ded4 into develop Oct 29, 2025
38 checks passed
@kmcgrady kmcgrady deleted the 10-18-add_visitor_pattern_to_appnodes_with_a_debug_visitor branch October 29, 2025 00:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

change:refactor PR contains code refactoring without behavior change impact:internal PR changes only affect internal code security-assessment-completed Security assessment has been completed for PR

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants