Skip to content

Conversation

@kmcgrady
Copy link
Collaborator

@kmcgrady kmcgrady commented Oct 19, 2025

Describe your changes

Now we will add some transiency. Transient is special, so we will implement a TransientNode. This change includes adding a method to every visitor and adding an implementation for the DebugVisitor.

Testing Plan

  • JS Unit Tests
  • There's no way this node is introduced yet, so there are no other tests.

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 19, 2025

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
Contributor

github-actions bot commented Oct 19, 2025

✅ PR preview is ready!

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

@kmcgrady kmcgrady added 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 labels Oct 19, 2025 — with Graphite App
@kmcgrady kmcgrady force-pushed the 10-19-convert_setin_to_setnodebydeltapathvisitor branch from 5cb8af0 to f5842b6 Compare October 24, 2025 15:05
@kmcgrady kmcgrady force-pushed the 10-19-introduce_transientnode branch 2 times, most recently from 8cafff6 to ec390ec Compare October 24, 2025 16:42
@kmcgrady kmcgrady force-pushed the 10-19-convert_setin_to_setnodebydeltapathvisitor branch 2 times, most recently from 9725ff2 to 9b06d11 Compare October 24, 2025 16:56
@kmcgrady kmcgrady force-pushed the 10-19-introduce_transientnode branch from ec390ec to 9f51720 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.8491% (20417 statements, 1460 missed)
  • Latest develop: 92.8491% (20417 statements, 1460 missed)

✅ Coverage change is within normal range.

Coverage by files
Name Stmts Miss Cover
streamlit/__init__.py 142 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 72 10 86%
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 97 13 87%
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 121 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 47 91%
streamlit/delta_generator.py 208 6 97%
streamlit/delta_generator_singletons.py 70 4 94%
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 200 15 92%
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 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 49 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 121 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 65 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 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 204 5 98%
streamlit/elements/widgets/button_group.py 171 1 99%
streamlit/elements/widgets/camera_input.py 62 10 84%
streamlit/elements/widgets/chat.py 195 54 72%
streamlit/elements/widgets/checkbox.py 52 0 100%
streamlit/elements/widgets/color_picker.py 59 2 97%
streamlit/elements/widgets/data_editor.py 245 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 390 19 95%
streamlit/elements/write.py 166 32 81%
streamlit/emojis.py 4 0 100%
streamlit/env_util.py 21 3 86%
streamlit/error_util.py 33 2 94%
streamlit/errors.py 191 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 460 91 80%
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/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 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 9 88%
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 92 8 91%
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 242 6 98%
streamlit/testing/v1/element_tree.py 1371 87 94%
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 59 14 76%
streamlit/web/server/websocket_headers.py 19 1 95%
TOTAL 20417 1460 93%

📊 View detailed coverage comparison

@github-actions
Copy link
Contributor

github-actions bot commented Oct 24, 2025

📉 Frontend coverage change detected

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

  • Current PR: 86.1000% (51339 lines, 7131 missed)
  • Latest develop: 86.1100% (51256 lines, 7118 missed)

✅ Coverage change is within normal range.

📊 View detailed coverage comparison

@kmcgrady kmcgrady force-pushed the 10-19-introduce_transientnode branch from 9f51720 to 24a81bd Compare October 24, 2025 17:47
@kmcgrady kmcgrady force-pushed the 10-19-convert_setin_to_setnodebydeltapathvisitor branch from 9b06d11 to 17554c5 Compare October 24, 2025 17:47
@kmcgrady kmcgrady force-pushed the 10-19-convert_setin_to_setnodebydeltapathvisitor branch from 3fc0e6d to db19c5d Compare November 2, 2025 22:42
@kmcgrady kmcgrady force-pushed the 10-19-introduce_transientnode branch 2 times, most recently from a093ee0 to 8056447 Compare November 2, 2025 22:53
@kmcgrady kmcgrady force-pushed the 10-19-convert_setin_to_setnodebydeltapathvisitor branch from db19c5d to 74be12a Compare November 2, 2025 22:53
@kmcgrady kmcgrady marked this pull request as ready for review November 2, 2025 23:18
@kmcgrady kmcgrady requested a review from Copilot November 2, 2025 23:18
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 a new TransientNode class to the render tree architecture, implementing the visitor pattern for this new node type across the codebase.

  • Adds TransientNode as a new type of AppNode that can hold transient element nodes with an optional anchor node
  • Updates the visitor pattern interface and all existing visitor implementations to support TransientNode
  • Includes comprehensive unit tests for the new TransientNode class and its debug visitor implementation

Reviewed Changes

Copilot reviewed 15 out of 15 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
frontend/lib/src/render-tree/TransientNode.ts New class implementing AppNode interface for transient nodes with anchor and transient element storage
frontend/lib/src/render-tree/TransientNode.test.ts Comprehensive unit tests for TransientNode constructor, visitor pattern, and debug output
frontend/lib/src/render-tree/visitors/AppNodeVisitor.interface.ts Adds visitTransientNode method to visitor interface
frontend/lib/src/render-tree/visitors/DebugVisitor.ts Implements visitTransientNode with tree-like debug output formatting and updates imports
frontend/lib/src/render-tree/visitors/DebugVisitor.test.ts Adds test coverage for TransientNode debug visitor functionality and updates imports
frontend/lib/src/render-tree/visitors/*.ts Stub implementations throwing "Method not implemented" errors for five visitor classes
frontend/lib/src/render-tree/visitors/RenderNodeVisitor.tsx Returns null for transient nodes with explanatory comment
frontend/lib/src/render-tree/BlockNode.test.ts Updates mock visitors to include visitTransientNode stub
frontend/lib/src/render-tree/ElementNode.test.ts Updates mock visitors to include visitTransientNode stub
frontend/lib/src/AppNode.ts Exports TransientNode and NO_SCRIPT_RUN_ID
frontend/lib/src/index.ts Adds TransientNode to public exports

@kmcgrady kmcgrady force-pushed the 10-19-convert_setin_to_setnodebydeltapathvisitor branch from 74be12a to fbcb3ff Compare November 2, 2025 23:50
@kmcgrady kmcgrady force-pushed the 10-19-introduce_transientnode branch from 8056447 to 89832ac Compare November 2, 2025 23:50
@kmcgrady kmcgrady force-pushed the 10-19-convert_setin_to_setnodebydeltapathvisitor branch from fbcb3ff to a1b1835 Compare November 4, 2025 00:50
@kmcgrady kmcgrady force-pushed the 10-19-introduce_transientnode branch from 89832ac to 7b3b980 Compare November 4, 2025 00:50
@kmcgrady kmcgrady force-pushed the 10-19-convert_setin_to_setnodebydeltapathvisitor branch from a1b1835 to ece38f4 Compare November 4, 2025 01:45
@kmcgrady kmcgrady force-pushed the 10-19-introduce_transientnode branch from 7b3b980 to 3aeebea Compare November 4, 2025 01:45
@kmcgrady kmcgrady force-pushed the 10-19-convert_setin_to_setnodebydeltapathvisitor branch from ece38f4 to e89016a Compare November 4, 2025 02:00
@kmcgrady kmcgrady force-pushed the 10-19-introduce_transientnode branch from 3aeebea to ba77f88 Compare November 4, 2025 02:00
@kmcgrady kmcgrady force-pushed the 10-19-convert_setin_to_setnodebydeltapathvisitor branch 2 times, most recently from 070a633 to 5ea7844 Compare November 4, 2025 02:14
@kmcgrady kmcgrady force-pushed the 10-19-introduce_transientnode branch from ba77f88 to 50071ab Compare November 4, 2025 02:14
Base automatically changed from 10-19-convert_setin_to_setnodebydeltapathvisitor to develop November 4, 2025 03:15
@kmcgrady kmcgrady force-pushed the 10-19-introduce_transientnode branch from 50071ab to 990657a Compare November 5, 2025 03:50
@kmcgrady kmcgrady force-pushed the 10-19-introduce_transientnode branch from 990657a to e49ccdb Compare November 23, 2025 01:50
Comment on lines +95 to +103
if (node.anchor) {
result += `${childPrefix}├── anchor:\n`
const anchorVisitor = new DebugVisitor(childPrefix + "│ ", true)
result += node.anchor.accept(anchorVisitor)
}

if (node.transientNodes.length > 0) {
result += `${childPrefix}└── transient nodes:\n`

Copy link
Contributor

Choose a reason for hiding this comment

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

The tree connector logic is incorrect. The anchor section always uses ├── (line 96), but if there are no transient nodes, the anchor should be the last child and use └── instead.

Impact: When a TransientNode has an anchor but no transient nodes, the debug output will show incorrect tree structure with dangling connectors.

Fix: The connector should be conditional:

if (node.anchor) {
  const hasTransientNodes = node.transientNodes.length > 0
  const anchorConnector = hasTransientNodes ? "├──" : "└──"
  result += `${childPrefix}${anchorConnector} anchor:\n`
  const anchorChildPrefix = childPrefix + (hasTransientNodes ? "│   " : "    ")
  const anchorVisitor = new DebugVisitor(anchorChildPrefix, true)
  result += node.anchor.accept(anchorVisitor)
}
Suggested change
if (node.anchor) {
result += `${childPrefix}├── anchor:\n`
const anchorVisitor = new DebugVisitor(childPrefix + "│ ", true)
result += node.anchor.accept(anchorVisitor)
}
if (node.transientNodes.length > 0) {
result += `${childPrefix}└── transient nodes:\n`
if (node.anchor) {
const hasTransientNodes = node.transientNodes.length > 0
const anchorConnector = hasTransientNodes ? "├──" : "└──"
result += `${childPrefix}${anchorConnector} anchor:\n`
const anchorChildPrefix = childPrefix + (hasTransientNodes ? "│ " : " ")
const anchorVisitor = new DebugVisitor(anchorChildPrefix, true)
result += node.anchor.accept(anchorVisitor)
}
if (node.transientNodes.length > 0) {
result += `${childPrefix}└── transient nodes:\n`

Spotted by Graphite Agent

Fix in Graphite


Is this helpful? React 👍 or 👎 to let us know.

Copy link
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 👍

@kmcgrady kmcgrady merged commit be29a02 into develop Dec 1, 2025
44 checks passed
@kmcgrady kmcgrady deleted the 10-19-introduce_transientnode branch December 1, 2025 15:28
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.

3 participants