feat(dashboard): notify users of available juice updates

Summary

Adds a dashboard widget on the Home (landing) page that displays the running version of OrangeQS Juice and warns users when a newer stable release is available on PyPI.

  • The check runs off the event loop, so it never blocks dashboard rendering.
  • The result is cached in runtime data, so the registry is polled at most once every 24h.
  • Network/registry failures are caught and logged — the widget falls back to a "last check failed" state with the last known result instead of crashing.
  • The version comparison, registry call, and widget are all generic and package-agnostic (configurable package name, registry URL, and docs link). Juice is just the first caller.

Closes #262 (closed)

What changed

  • client/package_registry.py (new) — generic, package-agnostic core:
    • fetch_release_versions(package, registry_url) — thin registry JSON-API client.
    • latest_stable_version(versions) — pure helper; ignores alpha/beta/rc/dev and unparseable versions.
    • is_newer_version(current, latest) — pure version comparison.
    • check_for_update(...) — cache-aware orchestrator: 24h polling interval + graceful failure fallback.
  • schemas/runtime.pyPackageUpdateState + PackageUpdateChecks (RuntimeData, keyed per package).
  • dashboard/widgets/update_notification_widget.py (new)UpdateNotificationWidget; runs the check via asyncio.to_thread + pn.state.execute and renders the banner into a bokeh Div.
  • dashboard/pages/home.py + home.html — instantiate and embed the widget.
  • pyproject.toml — add packaging dependency.

Screenshots

The widget rendered in each state — an update-available warning banner (with current/latest versions, last-checked timestamp, and a link to update instructions), the up-to-date state, the graceful "last check failed" fallback, and the initial loading state:

Screenshot 2026-06-05 at 1.13.48 AM.png

Reuse example

The widget works for any Python package:

import importlib.metadata
import panel
from orangeqs.juice.dashboard.widgets import UpdateNotificationWidget

widget = UpdateNotificationWidget(
    package_name="some-other-pkg",
    current_version=importlib.metadata.version("some-other-pkg"),
    docs_url="https://example.com/docs/upgrading",
    registry_url="https://pypi.org/pypi",  # any registry exposing the same JSON API
)
panel.state.execute(widget.check)            # off-event-loop check
template.add_panel("update_notification", widget.root)

Testing

  • tests/juice/client/test_package_registry.py:
    • Pure version parsing/comparison (no mocking) — pre-releases ignored, unparsable skipped.
    • Mocked-registry fetch (integration with a mock registry API).
    • Checker scenarios: poll + cache write, cache reuse within interval, re-poll after interval, graceful failure fallback, and "no stable release is not a failure".
  • Verified end to end against the live PyPI JSON API.
  • ruff (lint + format) and pyright clean on in-scope files; existing dashboard/settings suites pass.

Notes

  • A local dev/git build (e.g. 26.23.0+dirty) may show the banner against the matching stable release — expected, and consistent with "ignore pre-releases in the latest comparison".
  • The docs link points to the existing installation.html#updating section (no new docs page needed).

Merge request reports

Loading