From 749116143ee5c153504262579bc68b6041d18b1e Mon Sep 17 00:00:00 2001 From: Harsh Thakare Date: Fri, 7 Aug 2026 12:39:51 +0530 Subject: [PATCH] Bound and test Reflex compatibility window --- .github/workflows/ci.yml | 41 +++++++++++++++++++++++-- .github/workflows/release.yml | 6 ++-- docs/advanced/runtime-and-deployment.md | 3 +- pyproject.toml | 6 ++-- scripts/verify_ci_workflow.py | 31 +++++++++++++++++-- spec/process/production-readiness.md | 10 ++++-- 6 files changed, 82 insertions(+), 15 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index dd675577..f673201d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -238,6 +238,43 @@ jobs: - name: Benchmark smoke (1e5/1e6 — §12 harness runs every phase) run: .venv/bin/python scripts/bench.py --sizes 1e5,1e6 + reflex_compatibility: + name: Reflex compatibility (${{ matrix.reflex-version }}) + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + # 0.9.6 is the declared floor; 0.9.8 is the newest released version + # in the supported <0.10 compatibility window. + reflex-version: ["0.9.6", "0.9.8"] + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + fetch-depth: 0 + - uses: dtolnay/rust-toolchain@4be7066ada62dd38de10e7b70166bc74ed198c30 # stable + - uses: astral-sh/setup-uv@11f9893b081a58869d3b5fccaea48c9e9e46f990 # v8.3.2 + with: + enable-cache: false + - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 + with: + node-version: "22" + - name: Install exact Reflex compatibility target + run: | + npm ci + uv venv .venv + uv pip install -p .venv/bin/python \ + "reflex==${{ matrix.reflex-version }}" \ + "numpy>=1.24" "anywidget>=0.9" "pytest>=8" "aiohttp>=3.9" "uvicorn>=0.23" + uv pip install -p .venv/bin/python -e . --no-deps + - name: Verify exact Reflex target and metadata window + run: | + .venv/bin/python -c "import importlib.metadata as m; assert m.version('reflex') == '${{ matrix.reflex-version }}'" + .venv/bin/python -c "from packaging.requirements import Requirement; import importlib.metadata as m; req = next(Requirement(value) for value in m.metadata('xy').get_all('Requires-Dist') if Requirement(value).name == 'reflex'); assert str(req.specifier) == '<0.10,>=0.9.6'" + - name: Component compile and event smoke + run: .venv/bin/pytest -q tests/reflex_adapter/test_component.py::test_component_compiles_with_events + - name: State event and rebuild smoke + run: .venv/bin/pytest -q tests/reflex_adapter/test_state_bridge.py::test_rebuild_reads_session_state + browser_conformance: name: Accessibility + cross-browser conformance runs-on: ubuntu-latest @@ -645,7 +682,7 @@ jobs: XY_REQUIRE_CARGO: "1" run: | uv venv smoke-native - uv pip install --no-cache -p smoke-native dist/*.tar.gz numpy anywidget "reflex>=0.9.6" + uv pip install --no-cache -p smoke-native dist/*.tar.gz numpy anywidget "reflex>=0.9.6,<0.10" ./smoke-native/bin/python - <<'PY' import importlib.metadata as metadata @@ -666,7 +703,7 @@ jobs: XY_SKIP_CARGO: "1" run: | uv venv smoke-no-rust - uv pip install --no-cache -p smoke-no-rust dist/*.tar.gz numpy anywidget "reflex>=0.9.6" + uv pip install --no-cache -p smoke-no-rust dist/*.tar.gz numpy anywidget "reflex>=0.9.6,<0.10" ./smoke-no-rust/bin/python - <<'PY' import importlib.metadata as metadata diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index d04b68ac..8ffc8e93 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -124,7 +124,7 @@ jobs: shell: bash run: | uv venv smoke - uv pip install -p smoke dist/*.whl numpy anywidget "reflex>=0.9.6" + uv pip install -p smoke dist/*.whl numpy anywidget "reflex>=0.9.6,<0.10" ./smoke/bin/python -c "import importlib.metadata as m, reflex_xy, xy.kernels as k; assert k.BACKEND=='native', k.BACKEND; assert reflex_xy.__version__ == m.version('xy'); print('native', k.__file__)" \ || ./smoke/Scripts/python.exe -c "import importlib.metadata as m, reflex_xy, xy.kernels as k; assert k.BACKEND=='native', k.BACKEND; assert reflex_xy.__version__ == m.version('xy'); print('native')" - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 @@ -231,7 +231,7 @@ jobs: XY_REQUIRE_CARGO: "1" run: | uv venv smoke-native - uv pip install --no-cache -p smoke-native dist/*.tar.gz numpy anywidget "reflex>=0.9.6" + uv pip install --no-cache -p smoke-native dist/*.tar.gz numpy anywidget "reflex>=0.9.6,<0.10" ./smoke-native/bin/python - <<'PY' import importlib.metadata as metadata @@ -251,7 +251,7 @@ jobs: XY_SKIP_CARGO: "1" run: | uv venv smoke-no-rust - uv pip install --no-cache -p smoke-no-rust dist/*.tar.gz numpy anywidget "reflex>=0.9.6" + uv pip install --no-cache -p smoke-no-rust dist/*.tar.gz numpy anywidget "reflex>=0.9.6,<0.10" ./smoke-no-rust/bin/python - <<'PY' import importlib.metadata as metadata diff --git a/docs/advanced/runtime-and-deployment.md b/docs/advanced/runtime-and-deployment.md index bd89e5b7..15fa0cf9 100644 --- a/docs/advanced/runtime-and-deployment.md +++ b/docs/advanced/runtime-and-deployment.md @@ -27,7 +27,8 @@ interaction math. The integration is experimental and ships inside the published `xy` distribution. Install `xy[reflex]` so the environment also selects a supported -Reflex version, then import it as `reflex_xy`. See +Reflex version (`>=0.9.6,<0.10`), then import it as `reflex_xy`. The supported +window is tested at both its minimum and newest released version in CI. See [Deployment Recipes](/docs/xy/guides/deployment-recipes/). ## Choose a data tier diff --git a/pyproject.toml b/pyproject.toml index 82e286cf..e0b777cd 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -16,10 +16,10 @@ dependencies = [ [project.optional-dependencies] # The adapter source ships in every xy wheel so the framework integration and -# render client can never drift. The extra only selects the supported Reflex -# floor; importing plain `xy` remains framework-free. +# render client can never drift. The extra selects the tested Reflex window; +# importing plain `xy` remains framework-free. reflex = [ - "reflex>=0.9.6", + "reflex>=0.9.6,<0.10", ] [project.urls] diff --git a/scripts/verify_ci_workflow.py b/scripts/verify_ci_workflow.py index 2fe0cfd4..0f1aa79b 100644 --- a/scripts/verify_ci_workflow.py +++ b/scripts/verify_ci_workflow.py @@ -32,9 +32,11 @@ "sdist", "wheels", "install_without_rust", + "reflex_compatibility", } REQUIRED_CODSPEED_JOBS = {"benchmarks"} REQUIRED_RELEASE_JOBS = {"wheels", "sdist", "publish", "wasm"} +REFLEX_REQUIREMENT = "reflex>=0.9.6,<0.10" def _job_blocks(text: str) -> dict[str, str]: @@ -870,6 +872,29 @@ def validate_ci_workflow(path: Path = DEFAULT_CI_WORKFLOW) -> list[str]: "locked Reflex development environment", "uv sync --locked --extra reflex --group dev", ) + _require_job_contains( + errors, + jobs, + "reflex_compatibility", + "CI", + "minimum and maximum released Reflex compatibility matrix", + "fail-fast: false", + 'reflex-version: ["0.9.6", "0.9.8"]', + 'reflex==${{ matrix.reflex-version }}', + "<0.10,>=0.9.6", + "test_component.py::test_component_compiles_with_events", + "test_state_bridge.py::test_rebuild_reads_session_state", + ) + try: + project_metadata = (ROOT / "pyproject.toml").read_text(encoding="utf-8") + except OSError as exc: + errors.append(f"cannot read project metadata for Reflex compatibility gate: {exc}") + else: + if f'"{REFLEX_REQUIREMENT}"' not in project_metadata: + errors.append( + "pyproject.toml must bound the xy[reflex] extra to " + f"{REFLEX_REQUIREMENT!r}" + ) _require_job_contains( errors, jobs, @@ -1120,7 +1145,7 @@ def validate_ci_workflow(path: Path = DEFAULT_CI_WORKFLOW) -> list[str]: "Rust-backed sdist install contract", "XY_REQUIRE_CARGO", "uv pip install --no-cache", - '"reflex>=0.9.6"', + f'"{REFLEX_REQUIREMENT}"', "import reflex_xy", "import xy.kernels as kernels", 'kernels.BACKEND == "native"', @@ -1266,7 +1291,7 @@ def validate_release_workflow(path: Path = DEFAULT_RELEASE_WORKFLOW) -> list[str "scripts/verify_wheel.py", "--expect-native", "Install-size budget (<= 15 MB)", - '"reflex>=0.9.6"', + f'"{REFLEX_REQUIREMENT}"', "import importlib.metadata as m, reflex_xy", "assert reflex_xy.__version__ == m.version('xy')", "assert k.BACKEND=='native'", @@ -1331,7 +1356,7 @@ def validate_release_workflow(path: Path = DEFAULT_RELEASE_WORKFLOW) -> list[str "Rust-backed release sdist install contract", "XY_REQUIRE_CARGO", "uv pip install --no-cache", - '"reflex>=0.9.6"', + f'"{REFLEX_REQUIREMENT}"', "import reflex_xy", "import xy.kernels as kernels", 'kernels.BACKEND == "native"', diff --git a/spec/process/production-readiness.md b/spec/process/production-readiness.md index 0a2f00e7..05590e13 100644 --- a/spec/process/production-readiness.md +++ b/spec/process/production-readiness.md @@ -392,7 +392,7 @@ Before tagging a release: the documented native-core error. Wheel `METADATA` must keep `Name: xy`, `Requires-Python: >=3.11`, `anywidget>=0.9`, and `numpy>=1.24` as base requirements, plus - `Provides-Extra: reflex` and `reflex>=0.9.6` guarded by that extra. The wheel + `Provides-Extra: reflex` and `reflex>=0.9.6,<0.10` guarded by that extra. The wheel must contain `reflex_xy` and `XYChart.jsx`, and `RECORD` must list every archive file exactly once with matching `sha256` and size fields. Wheels and the sdist remain distribution/build-input-only: docs, tests, benchmarks, @@ -405,8 +405,12 @@ Every `xy` release carries the `reflex_xy` Python package and JSX wrapper. The wrapper links to the render client in the same installed distribution, so client, kernel, and framework bridge share one version. Plain `xy` must not install Reflex; `xy[reflex]` must install the declared supported floor. -Release smoke tests install Reflex, import `reflex_xy`, and assert that its -reported version matches the `xy` distribution version. +The supported Reflex window is `>=0.9.6,<0.10`. CI runs the component compile +and state-event rebuild smokes against both the minimum `0.9.6` and the newest +released `0.9.8` in that window. Widen the extra only after adding the new +released version to that matrix and passing both smokes. Release smoke tests +install the bounded extra, import `reflex_xy`, and assert that its reported +version matches the `xy` distribution version. ## Hardening Backlog