fix: percent-encode Var data in rx.download so # and % don't corrupt the file

Merged
BR
1mo ago
+54-1

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_encoded pins the generated expression.
  • tests/units/test_event.py::test_download_var_data_url_keeps_hash_and_percent evaluates the generated URL in Node, which the test skips if Node isn't installed, as tests/units/reflex_base/test_client_event_queue.py does. It reads the URL back with fetch and asserts the bytes are exactly the original JSON.
  • Both fail on main (fe2a78f4). The Node test fails with JSONDecodeError: Unterminated string because 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.