Skip to content

Conversation

@kmcgrady
Copy link
Collaborator

@kmcgrady kmcgrady commented Oct 18, 2025

Describe your changes

Migrates the filterMainScriptElements method to the FilterMainScriptElements visitor

Testing Plan

  • JS tests should be updated
  • E2E Tests (especially MPA) should be updated.

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-12813/streamlit-1.51.0-py3-none-any.whl
📦 @streamlit/component-v2-lib Download from artifacts
🕹️ Preview app pr-12813.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 18, 2025 — with Graphite App
@kmcgrady kmcgrady requested a review from Copilot October 18, 2025 22:09
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 migrates the filterMainScriptElements method from a class method on AppNode interface to a visitor pattern implementation (FilterMainScriptElementsVisitor). This change improves code organization by externalizing tree filtering logic into a dedicated visitor class.

Key Changes

  • Introduced FilterMainScriptElementsVisitor class that implements the AppNodeVisitor interface
  • Removed filterMainScriptElements method from AppNode interface, ElementNode, and BlockNode
  • Updated AppRoot.filterMainScriptElements to use the new visitor pattern

Reviewed Changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
FilterMainScriptElementsVisitor.ts New visitor class implementing the filtering logic with both instance and static methods
FilterMainScriptElementsVisitor.test.ts Comprehensive test suite covering visitor functionality, edge cases, and complex tree structures
ElementNode.ts Removed the filterMainScriptElements method
BlockNode.ts Removed the filterMainScriptElements method
AppRoot.ts Refactored to use the new visitor pattern with helper method ensureBlockNode
AppNode.interface.ts Removed the filterMainScriptElements method declaration from interface

Comment on lines +62 to +80
return new BlockNode(
node.activeScriptHash,
newChildren,
node.deltaBlock,
node.scriptRunId,
node.fragmentId,
node.deltaMsgReceivedAt
)
Copy link

Copilot AI Oct 18, 2025

Choose a reason for hiding this comment

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

Creating a new BlockNode instance on every visit creates unnecessary objects even when no children were filtered. Consider checking if newChildren has the same length and content as node.children before creating a new instance, returning the original node if nothing changed.

Copilot uses AI. Check for mistakes.
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I don't think this will handle nested architectures here.

Copy link
Collaborator

Choose a reason for hiding this comment

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

It seems this is already done at line 70.

@kmcgrady kmcgrady force-pushed the 10-18-migrate_filtermainscriptelements_to_filtermainscriptelementsvisitor branch 2 times, most recently from c630f8e to 11248f5 Compare October 18, 2025 22:15
@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.0100%

  • Current PR: 86.0400% (49907 lines, 6964 missed)
  • Latest develop: 86.0300% (49878 lines, 6964 missed)

✅ Coverage change is within normal range.

📊 View detailed coverage comparison

@kmcgrady kmcgrady force-pushed the 10-18-migrate_filtermainscriptelements_to_filtermainscriptelementsvisitor branch from 11248f5 to d07f3b1 Compare October 19, 2025 04:21
@kmcgrady kmcgrady force-pushed the 10-18-migrate_getin_to_getnodebydeltapathvisitor branch from b320d2a to 9f84e8e Compare October 19, 2025 04:21
@kmcgrady kmcgrady force-pushed the 10-18-migrate_filtermainscriptelements_to_filtermainscriptelementsvisitor branch from d07f3b1 to ba93847 Compare October 19, 2025 16:20
@kmcgrady kmcgrady force-pushed the 10-18-migrate_getin_to_getnodebydeltapathvisitor branch from 9f84e8e to 770c9d0 Compare October 19, 2025 16:20
@kmcgrady kmcgrady force-pushed the 10-18-migrate_filtermainscriptelements_to_filtermainscriptelementsvisitor branch from ba93847 to ff00c6d Compare October 19, 2025 16:47
@kmcgrady kmcgrady force-pushed the 10-18-migrate_getin_to_getnodebydeltapathvisitor branch from 770c9d0 to 5c01a6a Compare October 19, 2025 16:47
@kmcgrady kmcgrady force-pushed the 10-18-migrate_getin_to_getnodebydeltapathvisitor branch 2 times, most recently from 1788c2a to c52b408 Compare October 24, 2025 18:59
@kmcgrady kmcgrady force-pushed the 10-18-migrate_filtermainscriptelements_to_filtermainscriptelementsvisitor branch from d81a036 to 030ba55 Compare October 24, 2025 18:59
@kmcgrady kmcgrady marked this pull request as ready for review October 24, 2025 19:07
@kmcgrady kmcgrady force-pushed the 10-18-migrate_filtermainscriptelements_to_filtermainscriptelementsvisitor branch from 030ba55 to df8e8be Compare October 28, 2025 23:26
@kmcgrady kmcgrady force-pushed the 10-18-migrate_getin_to_getnodebydeltapathvisitor branch from c52b408 to b9afac2 Compare October 28, 2025 23:26
@kmcgrady kmcgrady force-pushed the 10-18-migrate_filtermainscriptelements_to_filtermainscriptelementsvisitor branch from df8e8be to ac65e88 Compare October 29, 2025 00:18
@kmcgrady kmcgrady force-pushed the 10-18-migrate_getin_to_getnodebydeltapathvisitor branch from b9afac2 to 3ecb1d2 Compare October 29, 2025 00:18
@kmcgrady kmcgrady force-pushed the 10-18-migrate_filtermainscriptelements_to_filtermainscriptelementsvisitor branch from ac65e88 to ec71eed Compare October 29, 2025 01:30
@kmcgrady kmcgrady force-pushed the 10-18-migrate_getin_to_getnodebydeltapathvisitor branch from 3ecb1d2 to 97426f6 Compare October 29, 2025 01:30

expect(result).toBeInstanceOf(BlockNode)
expect((result as BlockNode).activeScriptHash).toBe(MAIN_SCRIPT_HASH)
expect((result as BlockNode).children).toEqual([])
Copy link
Collaborator

Choose a reason for hiding this comment

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

[suggestion]: maybe use toHaveLength(0) instead?


const nonMatchingBlock = new BlockNode(
OTHER_SCRIPT_HASH,
[nonMatchingElement],
Copy link
Collaborator

Choose a reason for hiding this comment

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

[question] is it possible to have a matching element as child here, or no?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Likely not. Some magical voodoo under the hood would need to happen to cause this issue

expect(result).toBeUndefined()
})

it("filters children recursively when activeScriptHash matches", () => {
Copy link
Collaborator

Choose a reason for hiding this comment

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

[suggestion] I am not sure if this description matches the test. It seems to suggest we would be filtering a child of matching block. Is it a valid scenario that the matching block would also have a non-matching child element?

Is the idea of the test that we would still filter the non-matches when there is also one thing that matches? Maybe this name would be clearer -- "filters non-matching children and keeps matches". But maybe I am missing the significance of the resursiveness?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

So a block can have a non-matching element, so as long as the hash matches, it will need to continue down recursively.

The example would be

page = st.navigation(...)
with st.container(): # <- Block
    page.run()

the block would always match the main script hash, while the page will have different page hashes.

The other example you describe above was a non-matching block containing a matching element. I'm not sure how possible that is, BUT we just will delete the entire block since it doesn't match.

const result = visitor.visitBlockNode(parentBlock) as BlockNode

expect(result).toBeInstanceOf(BlockNode)
expect(result.children).toEqual([])
Copy link
Collaborator

Choose a reason for hiding this comment

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

[suggestion]: maybe use toHaveLength(0) instead?

expect(result.children[0]).toBe(matchingElement)
})

it("creates new visitor instance for each static call", () => {
Copy link
Collaborator

Choose a reason for hiding this comment

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

[suggestion/question]: should the title be something about the static method calls being idempotent? I don't understand the importance of referencing a new visitor instance (but let me know if I am missing something).

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 test is irrelevant really, so I removed it. It's hard to tell a new visitor instance is used.

MAIN_SCRIPT_HASH,
[
matchingElement,
nonMatchingElement,
Copy link
Collaborator

Choose a reason for hiding this comment

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

[nit] it bothers me that we are re-using the same objects here, but in practice these would be de-duped, right?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

So this is a shortcut. We could make duplicates, but the one thing about these Nodes is that they are immutable by design, so reusing them is not the a for this use case.

We do not dedupe nor would we expect references in multiple places on the tree, so I'll make the test more explicit.

expect(result3).toBe(element1) // Should still work
})
})
})
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggestion: Perhaps test can be added to cover childrenChanged optimization:

it("returns original node when no children change", () => {
  // All children match, so none are filtered
  const matchingElement1 = new ElementNode(
    text("element1").element,
    text("element1").metadata,
    "test_run_id",
    MAIN_SCRIPT_HASH
  )

  const matchingElement2 = new ElementNode(
    text("element2").element,
    text("element2").metadata,
    "test_run_id",
    MAIN_SCRIPT_HASH
  )

  const blockNode = new BlockNode(
    MAIN_SCRIPT_HASH,
    [matchingElement1, matchingElement2],
    block().deltaBlock,
    "test_run_id"
  )

  const visitor = new FilterMainScriptElementsVisitor(MAIN_SCRIPT_HASH)
  const result = visitor.visitBlockNode(blockNode)

  // Critical: Should return THE SAME reference (not a new object)
  // This is the childrenChanged optimization
  expect(result).toBe(blockNode)  // Reference equality!
  expect(result).not.toBe(new BlockNode(...))  // Not a new instance
})

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Good call. Added with some small modifications.

@kmcgrady kmcgrady force-pushed the 10-18-migrate_filtermainscriptelements_to_filtermainscriptelementsvisitor branch from ec71eed to e8e053c Compare October 30, 2025 03:59
@kmcgrady kmcgrady force-pushed the 10-18-migrate_getin_to_getnodebydeltapathvisitor branch from 97426f6 to 96013c8 Compare October 30, 2025 03:59
@kmcgrady kmcgrady force-pushed the 10-18-migrate_filtermainscriptelements_to_filtermainscriptelementsvisitor branch from e8e053c to e09e16d Compare October 30, 2025 12:48
@kmcgrady kmcgrady force-pushed the 10-18-migrate_getin_to_getnodebydeltapathvisitor branch from 96013c8 to 4e7ec2f Compare October 30, 2025 12:48
Base automatically changed from 10-18-migrate_getin_to_getnodebydeltapathvisitor to develop October 30, 2025 14:11
@kmcgrady kmcgrady force-pushed the 10-18-migrate_filtermainscriptelements_to_filtermainscriptelementsvisitor branch from e09e16d to 2c656d7 Compare October 30, 2025 14:19
@kmcgrady kmcgrady merged commit bc932bb into develop Oct 30, 2025
37 checks passed
@kmcgrady kmcgrady deleted the 10-18-migrate_filtermainscriptelements_to_filtermainscriptelementsvisitor branch October 30, 2025 17:29
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