[fix] inconsistent space encoding in query params#14691
[fix] inconsistent space encoding in query params#14691sfc-gh-lwilby merged 1 commit intodevelopfrom
Conversation
Align frontend bind="query-params" encoding to use + for spaces instead of %20, matching the backend st.query_params convention. Previously, frontend used encodeURIComponent (%20) while backend used urllib.parse.urlencode (+), causing confusing URL changes when both paths interacted. Fixes #14671 Made-with: Cursor Co-authored-by: lawilby <laura.wilby+oss@snowflake.com>
✅ 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
This PR fixes inconsistent URL space encoding when query params are written from both the frontend bind="query-params" flow and the backend st.query_params flow by aligning the frontend encoding to the backend’s +-for-space convention.
Changes:
- Post-process
query-stringoutput to replace%20with+when building query strings for bound widget params and when updating the URL. - Add unit tests to ensure spaces are encoded as
+(and never%20) for both URL updates andfilterParamsForPageChange.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated no comments.
| File | Description |
|---|---|
| frontend/lib/src/WidgetStateManager.ts | Normalizes space encoding by converting %20 → + after queryString.stringify() in the two relevant query-string construction paths. |
| frontend/lib/src/WidgetStateManager.test.ts | Adds coverage verifying the URL and page-change filtering paths encode spaces as + (not %20). |
There was a problem hiding this comment.
Summary
This PR fixes inconsistent space encoding in URL query parameters when using bind="query-params" on widgets. The frontend's query-string library encodes spaces as %20 (via encodeURIComponent), while the backend's urllib.parse.urlencode uses +. This mismatch caused the browser URL to visually flip between %20 and + encoding when both frontend widgets and backend st.query_params interact.
The fix post-processes queryString.stringify() output with .replaceAll("%20", "+") at both call sites in WidgetStateManager.ts — filterParamsForPageChange() (for page navigation) and updateUrlParam() (for live URL sync). Two new unit tests verify the corrected encoding.
Reviewer consensus: All three reviewers (claude-4.6-opus-high-thinking, gemini-3.1-pro, gpt-5.3-codex-high) approved this PR unanimously with no blocking issues or disagreements.
Code Quality
The fix is well-targeted, minimal, and follows existing patterns in the codebase. All reviewers agreed on this assessment. Only the two queryString.stringify() call sites that produce browser-visible URL strings are patched — the unrelated queryString.stringifyUrl() in ComponentInstance.tsx (for custom component iframe src) is correctly left untouched.
The .replaceAll("%20", "+") approach is safe because encodeURIComponent only produces %20 from actual space characters (literal %20 in a value would be double-encoded as %2520), and + signs in values are correctly preserved as %2B. Roundtrip correctness is maintained: query-string v9's parse() replaces + with space before decoding, so values survive the parse-modify-stringify cycle without corruption.
The inline comments explaining the replaceAll intent ("to match backend urllib.parse.urlencode encoding") are appropriate — they document the why, not the what.
Test Coverage
All reviewers agreed that test coverage is adequate. The PR adds two new unit tests to WidgetStateManager.test.ts:
"encodes spaces as + in URL, not %20"— tests theupdateUrlParampath"encodes spaces as + in filterParamsForPageChange, not %20"— tests the page navigation path
Both tests use positive + negative assertions (asserting + is present AND %20 is absent), following anti-regression testing best practices. The test mock setup correctly tracks URL state across replaceState calls.
No new e2e test is necessary — the fix is a mechanical encoding normalization where both + and %20 are semantically equivalent, and the existing e2e suite for query params would catch any functional regressions.
Backwards Compatibility
No breaking changes — all three reviewers concur. Both + and %20 are valid space representations in URL query strings per RFC 3986 and the application/x-www-form-urlencoded specification. The backend (urllib.parse.parse_qs) handles both forms correctly. Existing URLs containing %20 remain parseable; subsequent frontend writes now canonicalize spaces to +. The only observable change is cosmetic: the browser URL bar will now consistently show + instead of sometimes showing %20.
Security & Risk
No security concerns — unanimous agreement across all reviewers. The change is purely cosmetic URL encoding normalization. It does not affect data transmitted to the backend, authentication, session management, CSRF handling, external resource loading, or cross-origin behavior. Regression risk is low and limited to query-string serialization behavior, which is directly covered by the new tests.
External test recommendation
- Recommend external_test: No
- Triggered categories: None
- Evidence:
frontend/lib/src/WidgetStateManager.ts: Only changes URL query parameter encoding (cosmetic normalization of%20to+), no routing, auth, WebSocket, embedding, or security-sensitive behavior changes
- Suggested external_test focus areas: N/A
- Confidence: High (all three reviewers independently reached the same conclusion)
- Assumptions and gaps: None — the change is purely frontend-side URL string normalization with no server, networking, or cross-origin implications.
Accessibility
No accessibility impact. All reviewers agree. This PR changes URL encoding behavior only — no UI elements, ARIA attributes, focus management, keyboard interactions, or semantic structure are affected.
Recommendations
No blocking issues. One optional suggestion was raised:
- Optional (claude-4.6-opus-high-thinking only): Consider adding an explicit roundtrip unit test that sets a space-containing value, then updates a different widget, and verifies the original space value is still encoded as
+(not%2Bor%20). This would directly exercise theparse → modify → stringify → replaceAllcycle for space values. The other two reviewers did not raise this, and the existing test coverage is sufficient.
Verdict
APPROVED: All three reviewers (3/3) unanimously approved. The fix is clean, well-scoped, correctly normalizes frontend space encoding in query parameters to match the backend's + convention, and includes appropriate regression tests with no backwards compatibility or security concerns.
This is a consolidated AI review by claude-4.6-opus-high-thinking, synthesizing reviews from: claude-4.6-opus-high-thinking, gemini-3.1-pro, gpt-5.3-codex-high. All expected models completed their reviews successfully.
Summary
Fixes #14671
Root cause: The frontend
bind="query-params"path usesqueryString.stringify()(which internally usesencodeURIComponent, encoding spaces as%20), while the backendst.query_paramspath usesurllib.parse.urlencode()(which usesquote_plus, encoding spaces as+). When both paths interact, the URL flips between%20and+encoding.Fix: Post-process the output of
queryString.stringify()with.replaceAll("%20", "+")in bothbuildBoundQueryParams()andupdateUrlParam()to align with the backend's+encoding convention.Changes
WidgetStateManager.ts: Replace%20with+in the output of bothqueryString.stringify()call sitesWidgetStateManager.test.ts: Add 2 tests verifying spaces encode as+(not%20) in both the URL update and page change filter pathsTest plan
Made with Cursor