fix: percent-encode Var data in rx.download so # and % don't corrupt the file
All Submissions:
- Have you followed the guidelines stated in CONTRIBUTING.md file?
- Have you checked to ensure there aren't any other open Pull Requests for the desired changed?
Type of change
- Bug fix (non-breaking change which fixes an issue)
Changes To Core Features:
- Have you added an explanation of what your changes do and why you'd like us to include them?
- Have you written new tests for your core changes, as applicable?
- Have you successfully ran tests with your changes locally?
What goes wrong
When rx.download gets a state Var as data, it builds the URL as "data:<mime>," + JSON.stringify(var) and does not percent-encode it. In a data: URL, # starts the fragment, so everything after the first # in the JSON is dropped, and %XX sequences are decoded. The documented table example (rx.download(data=TableDownloadState.users, filename="data.json"), in docs/library/tables-and-data-grids/table.md) therefore downloads a truncated, invalid JSON file whenever a row contains a #, such as an address like 12 Main St #4 or a hex color.
Node's fetch follows the same data-URL rules as the browser:
data = [{"address": "12 Main St #4", "note": "100%25 sure"}]
before: [{"address":"12 Main St
after: [{"address":"12 Main St #4","note":"100%25 sure"}]
Fix
Wrap the stringified value in encodeURIComponent(...). Data that is already a data: URI is still passed through unchanged. The str and bytes paths already base64-encode their data, so they don't change.
Tests
tests/units/test_event.py::test_download_var_data_is_percent_encodedpins the generated expression.tests/units/test_event.py::test_download_var_data_url_keeps_hash_and_percentevaluates the generated URL in Node, which the test skips if Node isn't installed, astests/units/reflex_base/test_client_event_queue.pydoes. It reads the URL back withfetchand asserts the bytes are exactly the original JSON.- Both fail on
main(fe2a78f4). The Node test fails withJSONDecodeError: Unterminated stringbecause the download was cut off at#. Both pass with the fix.
Checks, all run locally:
uv run pytest tests/units: 9845 passed.pre-commit run --from-ref HEAD~1 --to-ref HEAD: ruff-format, ruff-check, codespell, update-pyi-files, pyright and ty all pass.- A news fragment is added under
packages/reflex-base/news/.
AI disclosure: an AI agent (Claude) found this bug and wrote the patch and tests, operating the breken-ai account. I reviewed the diff adversarially before opening this PR. I'll respond to review feedback, including Greptile/Copilot.