Refresh outdated dependencies flagged on the release branch

Merged
MA
2mo ago
+976-1433

Clears the backend and frontend outdated-dependency reports from the release branch's check-outdated-dependencies run (run 33216120758), ordered easiest-first so the load-bearing bumps are reviewable on their own.

Every backend candidate in that report was verified to be older than the 7-day exclude-newer window before being taken, so none of them are freshly-published churn.

#7037 has merged, so this is now just the pins. The outdated-check tooling was split out per review and landed separately; this branch was rebased onto the main that carries it, so the script, its tests and the workflow rewrite are no longer in this diff — only the dependency bumps and the [tool.check-outdated-deps] entries that go with them.

Type of change

  • Breaking change (fix or feature that would cause existing functionality to not work as expected) — the Bun minimum moves to 1.4.0; see below
  • Bug fix (non-breaking change which fixes an issue)

Commits

Refresh locked dependency versions and widen the wrapt bounduv lock --upgrade, starlette-admin 1.0 compat, wrapt <2.4 + stub workarounds, and two spent exclude-newer exemptions dropped
Narrow redis response types and stop orphaning a redis state managercorrect under redis-py 7.x and 8.x both; groundwork plus a genuine fixture leak
Bump frontend dependency pins and the bundled Bun to 1.4.0the frontend pins, the docs app's es-toolkit pin dropped, and the one breaking piece
update docs/app dependency pins (@masenf)the regenerated docs/app/reflex.lock
Record the new dependency holds in the outdated-check configthe ignore-list additions, in #7037's style

Bun 1.4.0 — the one breaking piece

MIN_VERSION has to move with VERSION. Bun 1.4 creates new lockfiles at lockfileVersion: 2, and 1.3.11 cannot parse a v2 lockfile at all; under --frozen-lockfile, the Reflex default, the install fails outright rather than degrading. Left at 1.3.0, the "bun already installed" fast path in install_bun would keep preferring a system 1.3.x over the pinned 1.4.0, and a project initialized under 1.4 would then break on a lockfile bun cannot read. Verified that with the minimum raised it declines a system 1.3.11 on PATH and installs the pinned 1.4.0.

The blast radius is narrower than "any 1.4 install breaks 1.3.x", and the warning text says so. Measured against both binaries:

bun 1.3.11  install                        -> lockfileVersion: 1
bun 1.4.0   add   (existing v1)            -> lockfileVersion: 1   (preserved)
bun 1.3.11  --frozen-lockfile on that lock -> OK
bun 1.4.0   install, no prior lock         -> lockfileVersion: 2

An existing v1 lockfile stays v1 under 1.4 even across dependency changes, so already-initialized projects are unaffected; only a lockfile generated from scratch is v2. The regenerated docs/app lock is itself evidence — it carries Bun 1.4's new configVersion field while staying at lockfileVersion: 1.

The redis 8 attempt, and why it was backed out

I originally widened redis >=6.4,<8.0 → <9.0. That is reverted. redis-py 8 hangs three redis-backed tests on Python 3.10 and 3.11, and one is not a test artifact: test_app.py::test_initialize_with_state hangs in the test body awaiting app.state_manager.get_state(...), parked in the event-loop selector. The other two hang in async-fixture and loop teardown.

A/B on one machine, nothing else running, against a flushed redis:

py3.11 + redis-py 7.4.1 →  0 hangs, 120s
py3.11 + redis-py 8.1.0 →  3 hangs (60s pytest-timeout each), 300s

In CI this presented as the unit-tests workflow being cancelled rather than failed — the redis step sat ~26 min on ubuntu 3.10/3.11 until the 30-min job timeout, while 3.12–3.14 finished it in 1–2 min. Because the conclusion is cancelled, it reads as "nothing failed" on the checks API and in the check-suite webhook, which is how it initially escaped my notice.

Tracked in #7026, which carries the full diagnosis: all three hanging tests, the A/B above, the reproduction recipe (note --timeout-method=signal is required — with thread the async hangs never get named), and the checklist ending in widening the bound and closing #6764. #6764 stays open as the request and links to #7026 as its blocker.

Two things are deliberately kept, since both are correct under 7.x and 8.x and pyright is clean on either: the response-type narrowing in istate/manager/redis.py and utils/token_manager.py, and the tests/units/conftest.py fix for an orphaned StateManagerRedis whose _lock_task blocked event-loop teardown (StateManager.create() ran before branching on the fixture param, so a live redis manager was built and discarded unclosed for the in_process/disk params). The leak is worth fixing on its own merits, but to be clear it did not resolve the redis-8 hangs.

Held back, with issues filed

Each entry in frontend_held / backend_held names its blocker:

  • tailwindcss / @tailwindcss/postcss (#7022) — I initially bumped these, which was wrong: the note on Constants.VERSION holds them at 4.3.0 because 4.3.1+ scans the git-ignored .web tree and collapses the docs prod build at scale, and I bumped straight past it. Reverted. 4.3.2/4.3.3 do not lift the hold — 4.3.1's tailwindlabs/tailwindcss#20214 ("scan explicitly referenced @source directories even when ignored by git") is the cause and is deliberate upstream behavior. My own validation could not have caught this: a small app builds fine on 4.3.3, and the failure is specifically a docs-scale collapse.
  • plotly.js 4.x (#7002) — removes scattermapbox/choroplethmapbox/densitymapbox, the mapbox subplot, and mapboxAccessToken, which is what PlotlyMapbox is built on. plotly.js-locales is held with it and listed explicitly: same repo, same pin, same blocker.
  • react-moment 2.x (#7003) — moves global config into a settings prop, restructures the props, drops filter, and adds a moment-duration-format peer dep.
  • redis 8.x (#7026, requested in #6764) — above.
  • pydantic-core and pyee — exact-pinned by pydantic and playwright, so they can only move when the parent does.

pyright and ruff stay in *_pinned rather than *_held: they are pinned by policy, not blocked on anything, so reaching latest never means their entry is removable. That distinction is why #7037 has two keys.

Follow-ups

  • #7024 — done, closed. docs/app/reflex.lock needed regenerating for the new pins; @masenf did it in the cherry-picked commit here. I verified the regenerated lock against every pin in this PR and they all match, with the deliberately-held ones still held and es-toolkit now transitive-only.
  • #7023 — open. Whether a below-minimum custom or REFLEX_USE_SYSTEM_BUN Bun should hard-fail rather than warn. The default path is already correct; the residual case is an explicit opt-in whose warning-only behavior is asserted by an existing test. This PR only makes the warning name the lockfile consequence.

Testing

  • uv run pytest tests/units with REFLEX_REDIS_URL set — 8158 passed, 5 skipped, 0 failed.
  • uv run pyright reflex tests — 0 errors.
  • uv run ruff check . / ruff format --check . — clean.
  • uv run python scripts/check_min_deps.py reflex — pass.
  • scripts/make_pyi.py leaves pyi_hashes.json unchanged.
  • uv lock --check clean.
  • Frontend pins validated by a clean production export of an app exercising the bumped components: Reflex installs Bun 1.4.0 to its own path, resolves every new pin, and a second export round-trips the lockfile through a frozen install and rebuilds.
  • Bun lockfile-version behavior measured directly against 1.3.11 and 1.4.0, as tabulated above.

On the rebase

The old history was 20 commits with three merges from main and a lot of self-cancelling churn (redis widened then backed out, tailwind bumped then reverted, the plotly ignore anchored then replaced by the script). Replaying that meant 32 commits conflicting from the second one, mostly resolving to "delete", so I rebuilt it as the five commits above instead. Verified equivalence rather than assuming it: the set of files changed relative to the base is identical to the old branch's, none lost and none extra, and all but two byte-identical — the two that differ are pyproject.toml and reflex/istate/proxy.py, both because main moved them. @masenf's docs-lock commit is cherry-picked so its authorship is preserved rather than folded into mine.

Still expected to report

The frontend job will still flag the reflex-enterprise-owned packages — @mantine/core, @xyflow/react, ag-charts-enterprise, ag-charts-react, react-colorful, rehype-sanitize. Those pins live in other repos, so I left them visible rather than adding them to frontend_pinned; ag-charts in particular looks like it belongs next to the existing ag-grid* entry, but that felt like your call rather than mine to bury.

🤖 Generated with Claude Code

https://claude.ai/code/session_01RKQ4ARC2vkE5iKoULyaczz