Skip to content

Conversation

@kmcgrady
Copy link
Collaborator

@kmcgrady kmcgrady commented Dec 1, 2025

Describe your changes

Added a mechanism to clear transient nodes from the render tree when rerunning a script. This ensures that transient elements don't persist inappropriately between runs.

The implementation:

  1. Added a clearTransientNodes method to AppRoot that replaces transient nodes with their anchors
  2. Created a new ClearTransientNodesVisitor class that handles the traversal and replacement logic
  3. Added logic to respect fragment IDs when clearing, so only relevant fragments are processed
  4. Updated the App component to call this method before executing a script run

Testing Plan

  • Unit Tests: Added comprehensive tests for the new functionality:
    • Tests for AppRoot.clearTransientNodes to verify it correctly delegates to the visitor
    • Tests for ClearTransientNodesVisitor to verify it correctly handles different node types
    • Tests to verify fragment ID filtering works as expected

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 Dec 1, 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 Dec 1, 2025

✅ PR preview is ready!

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

@kmcgrady kmcgrady force-pushed the 10-19-implement_transient_spinners branch from 166ebfd to 9966041 Compare December 1, 2025 15:47
@kmcgrady kmcgrady force-pushed the 11-24-add_ability_to_clear_transient_nodes_when_the_script_is_run branch from 49e6614 to 381caa5 Compare December 1, 2025 15:47
@kmcgrady kmcgrady force-pushed the 10-19-implement_transient_spinners branch from 9966041 to 7a0fd9d Compare December 1, 2025 16:55
@kmcgrady kmcgrady force-pushed the 11-24-add_ability_to_clear_transient_nodes_when_the_script_is_run branch 2 times, most recently from 871237f to 4ee9200 Compare December 1, 2025 17:39
@kmcgrady kmcgrady force-pushed the 10-19-implement_transient_spinners branch from 7a0fd9d to 3da75d7 Compare December 1, 2025 17:39
@kmcgrady kmcgrady force-pushed the 11-24-add_ability_to_clear_transient_nodes_when_the_script_is_run branch from 4ee9200 to 48bbf15 Compare December 1, 2025 18:23
@kmcgrady kmcgrady force-pushed the 10-19-implement_transient_spinners branch from 3da75d7 to 58d3103 Compare December 1, 2025 18:23
@kmcgrady kmcgrady force-pushed the 11-24-add_ability_to_clear_transient_nodes_when_the_script_is_run branch from 48bbf15 to 5d534f8 Compare December 1, 2025 18:57
@kmcgrady kmcgrady force-pushed the 10-19-implement_transient_spinners branch from 58d3103 to bc67db4 Compare December 1, 2025 18:57
@kmcgrady kmcgrady force-pushed the 11-24-add_ability_to_clear_transient_nodes_when_the_script_is_run branch from 5d534f8 to 112771e Compare December 2, 2025 02:06
@kmcgrady kmcgrady changed the base branch from 10-19-implement_transient_spinners to graphite-base/13166 December 2, 2025 02:06
@kmcgrady kmcgrady force-pushed the 11-24-add_ability_to_clear_transient_nodes_when_the_script_is_run branch from 44ddeb0 to 35899df Compare December 8, 2025 15:43
@kmcgrady kmcgrady force-pushed the 10-19-implement_transient_spinners branch from 45e1c2d to 3ec60af Compare December 8, 2025 15:43
@kmcgrady kmcgrady force-pushed the 11-24-add_ability_to_clear_transient_nodes_when_the_script_is_run branch from 35899df to 88c3674 Compare December 8, 2025 16:18
@kmcgrady kmcgrady force-pushed the 10-19-implement_transient_spinners branch from 3ec60af to 9484285 Compare December 8, 2025 16:18
@kmcgrady kmcgrady force-pushed the 11-24-add_ability_to_clear_transient_nodes_when_the_script_is_run branch from 88c3674 to f8815fa Compare December 8, 2025 16:25
@kmcgrady kmcgrady force-pushed the 10-19-implement_transient_spinners branch from 9484285 to df0d3ed Compare December 8, 2025 16:25
@github-actions
Copy link
Contributor

github-actions bot commented Dec 8, 2025

📉 Python coverage change detected

The Python unit test coverage has decreased by 0.0047%

  • Current PR: 92.6944% (21203 statements, 1549 missed)
  • Latest develop: 92.6991% (21203 statements, 1548 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 190 35 82%
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 70 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 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 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 152 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 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 130 1 99%
streamlit/dataframe_util.py 506 51 90%
streamlit/delta_generator.py 250 7 97%
streamlit/delta_generator_singletons.py 74 7 91%
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 201 15 93%
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 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 391 26 93%
streamlit/elements/lib/color_util.py 100 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 90 0 100%
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 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 44 3 93%
streamlit/elements/text.py 16 0 100%
streamlit/elements/toast.py 26 0 100%
streamlit/elements/vega_charts.py 238 3 99%
streamlit/elements/widgets/__init__.py 0 0 100%
streamlit/elements/widgets/audio_input.py 68 10 85%
streamlit/elements/widgets/button.py 245 6 98%
streamlit/elements/widgets/button_group.py 171 1 99%
streamlit/elements/widgets/camera_input.py 62 10 84%
streamlit/elements/widgets/chat.py 237 58 76%
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/file_uploader.py 108 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 103 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 194 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 171 3 98%
streamlit/runtime/caching/cache_errors.py 45 4 91%
streamlit/runtime/caching/cache_resource_api.py 146 1 99%
streamlit/runtime/caching/cache_type.py 11 1 91%
streamlit/runtime/caching/cache_utils.py 167 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 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 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 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 193 12 94%
streamlit/runtime/pages_manager.py 59 2 97%
streamlit/runtime/runtime.py 251 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 134 2 99%
streamlit/runtime/secrets.py 242 25 90%
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 134 5 96%
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 440 33 92%
streamlit/runtime/state/session_state_proxy.py 62 8 87%
streamlit/runtime/state/widgets.py 15 1 93%
streamlit/runtime/stats.py 138 21 85%
streamlit/runtime/theme_util.py 46 1 98%
streamlit/runtime/uploaded_file_manager.py 39 3 92%
streamlit/runtime/websocket_session_manager.py 97 0 100%
streamlit/source_util.py 36 1 97%
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 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 148 16 89%
streamlit/url_util.py 39 5 87%
streamlit/user_info.py 118 10 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 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 42 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 153 20 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 42 5 88%
streamlit/web/server/bidi_component_request_handler.py 65 8 88%
streamlit/web/server/browser_websocket_handler.py 129 40 69%
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 131 23 82%
streamlit/web/server/oidc_mixin.py 46 0 100%
streamlit/web/server/routes.py 90 7 92%
streamlit/web/server/server.py 188 11 94%
streamlit/web/server/server_util.py 68 5 93%
streamlit/web/server/stats_request_handler.py 59 5 92%
streamlit/web/server/upload_file_request_handler.py 59 14 76%
streamlit/web/server/websocket_headers.py 19 1 95%
TOTAL 21203 1549 93%

📊 View detailed coverage comparison

@kmcgrady kmcgrady force-pushed the 10-19-implement_transient_spinners branch from df0d3ed to e6b4f56 Compare December 8, 2025 17:50
@kmcgrady kmcgrady force-pushed the 11-24-add_ability_to_clear_transient_nodes_when_the_script_is_run branch from f8815fa to 997f776 Compare December 8, 2025 17:50
@kmcgrady kmcgrady force-pushed the 10-19-implement_transient_spinners branch from e6b4f56 to 75bb6e7 Compare December 8, 2025 17:52
@kmcgrady kmcgrady force-pushed the 11-24-add_ability_to_clear_transient_nodes_when_the_script_is_run branch from 997f776 to 6650f56 Compare December 8, 2025 17:52
Base automatically changed from 10-19-implement_transient_spinners to develop December 8, 2025 18:10
@kmcgrady kmcgrady requested a review from a team as a code owner December 8, 2025 18:10
@lukasmasuch
Copy link
Collaborator

Something seems broken with this PR, it seems to contain all the changes from the already merged #12826. Maybe try to bring it up-to-date with develop?

@kmcgrady kmcgrady force-pushed the 11-24-add_ability_to_clear_transient_nodes_when_the_script_is_run branch from 6650f56 to 87ca7e4 Compare December 8, 2025 18:32
@kmcgrady kmcgrady force-pushed the 11-24-add_ability_to_clear_transient_nodes_when_the_script_is_run branch from 87ca7e4 to ca76a32 Compare January 5, 2026 18:35
Copilot AI review requested due to automatic review settings January 5, 2026 18:35
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 implements a mechanism to clear transient nodes from the render tree when a script is rerun, preventing transient elements from persisting inappropriately between script executions.

Key Changes:

  • Introduces a new visitor pattern implementation (ClearTransientNodesVisitor) that traverses the render tree and replaces transient nodes with their anchors
  • Adds fragment ID filtering to ensure only relevant fragments are processed during partial reruns
  • Integrates the clearing logic into the App component to execute before script runs

Reviewed changes

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

Show a summary per file
File Description
frontend/lib/src/render-tree/visitors/ClearTransientNodeVisitor.ts New visitor class that implements the logic to replace transient nodes with their anchor nodes, respecting fragment ID filtering
frontend/lib/src/render-tree/visitors/ClearTransientNodeVisitor.test.ts Comprehensive test suite covering visitor behavior for element nodes, transient nodes, block nodes, and fragment ID filtering
frontend/lib/src/render-tree/AppRoot.ts Adds clearTransientNodes method that delegates to the new visitor to clear transient nodes from the render tree
frontend/lib/src/render-tree/AppRoot.test.ts Tests for the new clearTransientNodes method, verifying it correctly clears transient nodes and respects fragment IDs
frontend/app/src/App.tsx Integrates transient node clearing into the script run flow by calling clearTransientNodes before updating the script run ID
frontend/lib/src/render-tree/visitors/ClearStaleNodeVisitor.ts Updates import path for AppNodeVisitor to use a relative import, improving consistency with other visitor files

@kmcgrady kmcgrady force-pushed the 11-24-add_ability_to_clear_transient_nodes_when_the_script_is_run branch from ca76a32 to aa1e7f8 Compare January 5, 2026 18:51
@kmcgrady kmcgrady added the ai-review If applied to PR or issue will run AI review workflow label Jan 5, 2026
@github-actions github-actions bot removed the ai-review If applied to PR or issue will run AI review workflow label Jan 5, 2026
@github-actions
Copy link
Contributor

github-actions bot commented Jan 5, 2026

Summary

This PR adds the ability to clear transient nodes from the render tree when a script is re-run. The implementation:

  1. Adds a new ClearTransientNodesVisitor class that traverses the tree and replaces transient nodes with their anchor nodes
  2. Adds a clearTransientNodes method to AppRoot that delegates to the visitor
  3. Updates App.tsx to call clearTransientNodes before executing a script run
  4. Includes a minor import path fix in ClearStaleNodeVisitor.ts

The purpose is to ensure transient elements (like status messages, spinners) don't persist inappropriately between script runs.

Code Quality

Overall Assessment

The code follows the existing visitor pattern well and is consistent with ClearStaleNodeVisitor. The implementation is clean and includes a performance optimization that returns the same node reference when children haven't changed.

Issues Found

1. Naming Inconsistency (Minor)

  • File: ClearTransientNodeVisitor.ts (singular "Node")
  • Class: ClearTransientNodesVisitor (plural "Nodes")

This is inconsistent with ClearStaleNodeVisitor.ts where both file and class use singular. Recommend renaming the class to ClearTransientNodeVisitor for consistency.

2. App.tsx setState Pattern (Minor Observation)
The change in App.tsx (lines 1369-1373) correctly uses the updater function pattern for setState to access prevState.elements. This is the proper way to handle state updates that depend on previous state.

this.setState(prevState => ({
  elements: prevState.elements.clearTransientNodes(fragmentIdsThisRun),
  scriptRunId,
}))

This is correct and follows React best practices.

Test Coverage

Strengths

  • ClearTransientNodeVisitor.test.ts: Comprehensive unit tests covering:

    • Element nodes returned unchanged
    • Transient nodes replaced with their anchors
    • Block node recursion
    • Performance optimization (same node reference when no changes)
    • Fragment ID filtering with fragmentIdsThisRun
    • Blocks without fragmentId still get traversed
  • AppRoot.test.ts: Integration tests for clearTransientNodes covering:

    • Basic clearing behavior (transient → anchor)
    • Fragment ID filtering

Potential Gaps

  1. No explicit test for TransientNode with undefined anchor: While implicitly covered by the "respects fragmentIdsThisRun when clearing" test in AppRoot.test.ts (where the transient at [0, 2, 0] has no anchor and returns undefined), an explicit unit test in ClearTransientNodeVisitor.test.ts would improve clarity:
it("returns undefined when transient node has no anchor", () => {
  const transientWithNoAnchor = new TransientNode(
    "script_run_id",
    undefined, // no anchor
    [text("Transient")],
    Date.now()
  )
  const visitor = new ClearTransientNodesVisitor([])
  expect(visitor.visitTransientNode(transientWithNoAnchor)).toBeUndefined()
})
  1. E2E Test Coverage: No E2E tests are included. While this may be acceptable for internal render-tree logic, consider whether end-to-end verification of transient clearing behavior is needed for user-facing scenarios.

Backwards Compatibility

Assessment: No Breaking Changes

  • The clearTransientNodes method is additive to AppRoot
  • The new visitor follows the established interface pattern
  • The App.tsx change is internal to the app component
  • No public API changes

Security & Risk

Low Risk

  • No external input handling
  • No security-sensitive operations
  • The change is isolated to render tree manipulation
  • Follows immutable data patterns (creates new nodes rather than mutating)

Potential Risk: Fragment ID Handling

The fragment ID filtering logic mirrors ClearStaleNodeVisitor, which is good for consistency. However, ensure this aligns with the expected behavior when running fragments:

  • If fragmentIdsThisRun is empty/undefined, all transient nodes are cleared
  • If fragmentIdsThisRun contains IDs, only blocks with matching fragment IDs (or no fragment ID) are traversed

This appears to be the intended behavior based on the test cases.

Recommendations

  1. Rename class for consistency (Minor)

    In frontend/lib/src/render-tree/visitors/ClearTransientNodeVisitor.ts:

    • Rename ClearTransientNodesVisitorClearTransientNodeVisitor (singular)
    • This matches the file name and is consistent with ClearStaleNodeVisitor
  2. Add explicit test for undefined anchor (Minor)

    In ClearTransientNodeVisitor.test.ts, add a test case for transient nodes without anchors:

    it("returns undefined when transient node has no anchor", () => {
      const transientWithNoAnchor = new TransientNode(
        "script_run_id",
        undefined,
        [text("Transient")],
        Date.now()
      )
      const visitor = new ClearTransientNodesVisitor([])
      expect(visitor.visitTransientNode(transientWithNoAnchor)).toBeUndefined()
    })
  3. Consider adding JSDoc to the public method

    The clearTransientNodes method in AppRoot.ts could benefit from a brief JSDoc comment explaining when/why it's called, similar to clearStaleNodes.

Verdict

APPROVED: The implementation is solid, follows established patterns, and includes comprehensive test coverage. The naming inconsistency and missing explicit test case for undefined anchors are minor issues that don't block merging. The changes are backwards compatible and pose no security risks.


This is an automated AI review. Please verify the feedback and use your judgment.

@kmcgrady kmcgrady force-pushed the 11-24-add_ability_to_clear_transient_nodes_when_the_script_is_run branch from aa1e7f8 to 3f26b82 Compare January 5, 2026 20:54
@lukasmasuch
Copy link
Collaborator

@cursor review

Copy link

@cursor cursor bot left a comment

Choose a reason for hiding this comment

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

✅ Bugbot reviewed your changes and found no bugs!

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 👍

@sfc-gh-kmcgrady sfc-gh-kmcgrady merged commit 7fb3d93 into develop Jan 8, 2026
44 checks passed
@sfc-gh-kmcgrady sfc-gh-kmcgrady deleted the 11-24-add_ability_to_clear_transient_nodes_when_the_script_is_run branch January 8, 2026 16:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

change:bugfix PR contains bug fix implementation impact:users PR changes affect end users 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