[Fix] Restore bound query params in URL with MPA#14374
[Fix] Restore bound query params in URL with MPA#14374sfc-gh-mbarnes merged 5 commits intodevelopfrom
Conversation
✅ Snyk checks have passed. No issues have been found so far.
💻 Catch issues earlier using the plugins for VS Code, JetBrains IDEs, Visual Studio, and Eclipse. |
✅ PR preview is ready!
|
There was a problem hiding this comment.
Pull request overview
Fixes a multipage-app regression where widgets bound via bind="query-params" could keep their persisted value in st.session_state after page navigation, but lose the corresponding URL query parameter—breaking URL shareability and consistency.
Changes:
- Preserve stale bound keyed widget values across MPA page transitions by saving them under user keys during
_remove_stale_widgets. - Restore missing URL query params in
register_widgetfor bound widgets when a persisted non-default value exists (while keeping default collapsing behavior). - Normalize boolean query param serialization to
true/falseto match common URL conventions.
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| lib/streamlit/runtime/state/session_state.py | Preserves bound values across stale cleanup and re-syncs missing URL params on remount; tracks bound intent across page transitions. |
| lib/streamlit/runtime/state/query_params.py | Ensures _set_corrected_value serializes bools as lowercase true/false. |
| lib/tests/streamlit/runtime/state/session_state_test.py | Adds unit tests covering preservation + remount URL sync behavior, plus required context mocking. |
| lib/tests/streamlit/runtime/state/query_params_test.py | Extends scalar correction tests to include boolean normalization. |
| e2e_playwright/multipage_apps/mpa_basics_test.py | Adds E2E regression test verifying query param restoration after navigating away and back. |
You can also share your feedback on Copilot code review. Take the survey.
This comment was marked as duplicate.
This comment was marked as duplicate.
SummaryThis PR fixes a bug where widget-bound query parameters (
Code QualityAll three reviewers agree the code is well-structured, clean, and follows existing Streamlit patterns. Key strengths highlighted across reviews:
Minor observations (non-blocking):
Test CoverageAll three reviewers agree test coverage is thorough and well-organized:
Backwards CompatibilityAll three reviewers agree there are no breaking changes:
Security & RiskAll three reviewers agree there are no security concerns. No changes to auth, cookies, CSRF/XSRF, WebSocket handshake, headers, asset serving, external network calls, file system operations, or eval/exec usage. The main risk is behavioral regression in URL/session synchronization logic, which is substantially mitigated by the added tests. External test recommendation
Reviewer disagreement: gpt-5.3-codex-high recommended external testing (medium confidence) citing "routing and URL behavior" as a triggered category, suggesting validation under reverse-proxy and iframe-embedded modes. gemini-3.1-pro and opus-4.6-thinking both assessed no external test needed (high confidence), reasoning that query parameter behavior is entirely within the Streamlit session/app boundary and doesn't interact with proxy, iframe, or cross-origin layers. The consolidation sides with the majority: the changes are to internal Python session state management and don't alter HTTP-layer routing, URL rewriting, or embedding behavior. However, gpt-5.3's suggestion to add an external test as a follow-up hardening step is reasonable and could be considered for future work. AccessibilityAll three reviewers agree: No frontend changes are included in this PR. All changes are to Python backend code and tests. No accessibility impact. Recommendations
VerdictAPPROVED: All three reviewers unanimously approved this PR. The fix is well-crafted, addressing the root causes of the MPA query-param loss issue with a multi-layered approach (value preservation + URL sync + default collapsing). Test coverage is comprehensive across unit and E2E layers. No backwards-compatibility, security, or accessibility concerns. Minor nits are non-blocking. This is a consolidated AI review by 📋 Review by `gemini-3.1-pro`SummaryThis PR fixes an issue where bound query parameters ( Code QualityThe code changes are well-structured, clean, and well-documented.
Test CoverageThe changes are excellently tested.
Backwards CompatibilityThe PR introduces a slight behavioral change for boolean query parameters:
Security & RiskNo security concerns or regression risks identified. The changes are confined to state management and URL synchronization for bound widgets. External test recommendation
AccessibilityNo frontend changes were made in this PR, so accessibility is not impacted. RecommendationsThe PR is solid and ready to be merged. Great job on the detailed comments explaining the "why" behind the state management logic! VerdictAPPROVED: The changes elegantly fix the MPA query param bug with comprehensive test coverage and no apparent risks. This is an automated AI review by 📋 Review by `gpt-5.3-codex-high`SummaryThis PR fixes a real multipage regression where query-param-bound widget values persist in session state but disappear from the URL after navigating away and back. The implementation preserves bound stale widget values across page transitions and restores missing non-default URL params during remount, while keeping default-value URL collapsing behavior. Code QualityThe changes are well-structured and localized:
No blocking code-quality issues identified. Test CoverageCoverage is strong for this scope:
Given the feature scope, unit + e2e coverage is adequate. Backwards CompatibilityBackwards compatibility looks good:
Security & RiskNo direct security issues found in this diff.
External test recommendation
AccessibilityNo frontend UI/component changes were introduced in this PR; accessibility impact is minimal and no new a11y issues were identified from the changed files. Recommendations
VerdictAPPROVED: The fix is well-targeted, regression-tested, and appears safe to merge, with external-mode coverage recommended as a follow-up hardening step. This is an automated AI review by 📋 Review by `opus-4.6-thinking`SummaryThis PR fixes a bug where widget-bound query parameters ( Key changes:
Code QualityThe code is well-structured and follows existing Streamlit patterns. Comments clearly explain the "why" behind each change, which is valuable given the complexity of MPA state management. Strengths:
Minor observations:
Test CoverageTest coverage is thorough and well-organized: Unit tests (session_state_test.py):
Unit tests (query_params_test.py):
E2E test (mpa_basics_test.py):
The tests follow the AGENTS.md guidelines well: docstrings on all test functions, anti-regression negative checks, appropriate use of Backwards CompatibilityNo breaking changes:
Security & RiskNo security concerns identified:
AccessibilityNo frontend changes are included in this PR. All changes are to Python backend code and tests. No accessibility impact. Recommendations
VerdictAPPROVED: This is a well-crafted bug fix with thorough test coverage, clean implementation, and no backwards-compatibility or security concerns. The multi-layered approach (value preservation + URL sync + default collapsing) correctly addresses the root causes of the MPA query-param loss issue. This is an automated AI review by |
Describe your changes
When a widget uses
bind="query-params"in a multi-page app, navigating away and back causes the URL parameter to be lost even though the widget's value persists in session state. This breaks URL shareability — the widget displays the correct value but the URL no longer reflects it.The root cause is two-fold:
_remove_stale_widgetsdeletes the widget's value from_old_stateduring page transitions (stored under the widget ID, which is an element ID that gets filtered as stale), with no mechanism to preserve it for later remount.register_widgethas no code path to restore a missing URL parameter from a persisted non-default widget value.Changes:
_remove_stale_widgets): Before the stale filter runs, save bound keyed widget values under their user keys in_old_state. User keys are not element IDs, so they survive the filter. A durable_query_param_bound_widget_idsset tracks bound intent across page transitions where bindings and metadata may already be cleaned up.register_widget): After value resolution, restore the URL parameter whenbind="query-params"is active, the param is missing, the value is non-default, and no programmaticst.session_stateset occurred this run. Default-value collapsing is preserved (defaults never appear in URL)._set_corrected_value): Writetrue/falseinstead ofTrue/Falsefor bool query params to match URL conventions.GitHub Issue
Closes #14350
Testing Plan