Skip to content

feat(cli): nudge raven upgrade in the tui status bar when behind - #220

Open
arelchan wants to merge 2 commits into
mainfrom
feat/tui_update_notice
Open

feat(cli): nudge raven upgrade in the tui status bar when behind#220
arelchan wants to merge 2 commits into
mainfrom
feat/tui_update_notice

Conversation

@arelchan

Copy link
Copy Markdown
Contributor

Summary

The TUI already read update_behind / update_command out of the session init
bundle, but nothing on the Python side ever filled those fields, so the hint
never appeared. This wires up the missing half and moves where it shows.

The check

raven/cli/update_notice.py compares the running version against a small
cache in ~/.raven/update_check.json:

  • raven tui calls maybe_refresh_async() once per launch. It spawns a daemon
    thread only when the cache is missing or older than 24h, so a normal launch
    touches the network at most once a day and never blocks startup.
  • The gateway only reads that cache when it builds the session info bundle
    (_default_session_info). Reading is pure and fast; no network on the
    session-create path, which is why the check is cached rather than live.
  • Every failure path is swallowed: no cache, unparseable version, corrupt JSON,
    network error, rate limit. An update hint must never break startup.

The tradeoff is staleness by one launch: the first launch after a release
lands refreshes the cache, and the hint shows on the next launch.

Where it shows

The status bar's right slot, in the warn color, replacing the cwd/branch label
while an update is available. No extra line, no layout shift.

The old banner block in branding.tsx is removed. The banner is the first
thing on screen at startup and an upgrade nudge does not deserve that weight.
update_behind is now a flag rather than a commit count, so the text is
version-agnostic (Update available - run raven upgrade) instead of the old
N commits behind.

Type

  • Fix
  • Feature
  • Docs
  • CI / tooling
  • Refactor
  • Other

Verification

uv run ruff check raven/ tests/                  # All checks passed
uv run ruff format --check raven/ tests/         # 760 files already formatted
uv run pytest tests/test_cli_update_notice.py \
  tests/test_cli_tui_commands.py \
  tests/test_tui_rpc_session.py -q               # 95 passed
cd ui-tui && npm run type-check                  # clean
cd ui-tui && npm test                            # 904 passed | 3 skipped
cd ui-tui && npm run lint                        # 0 errors
cd ui-tui && npx prettier --check "src/**/*.{ts,tsx}"   # clean

New tests cover the cache read/write and refresh throttle
(tests/test_cli_update_notice.py), the status-bar slot swap
(ui-tui/src/__tests__/statusRule.test.tsx), and the banner no longer
rendering the block (branding.test.tsx).

Also exercised the five edge cases by hand: absent cache, newer cached
release, cache equal to current, unparseable version string, and a corrupt
cache file. Only the second one returns a notice; the rest return None.

The lint warning reported on appChrome.tsx (react-hooks/exhaustive-deps,
line 275) is pre-existing on main and untouched by this change.

  • Relevant tests pass locally
  • Relevant lint / type checks pass locally
  • User-facing docs or screenshots are updated when needed

Risk

  • Security impact considered
  • Backward compatibility considered
  • Rollback path is clear for risky changes

The GitHub releases fetch reuses upgrade_commands._fetch_latest_release, so
no new network surface or credential handling. The cache file holds a version
string and a timestamp only.

Wire-compatible: update_behind / update_command were already optional
fields in the info bundle, and a client that never receives them behaves as
before. Rollback is reverting this commit; the feature degrades to its current
state, which is showing nothing.

To see it locally:

printf '{"latest_version":"9.9.9","checked_at":%s}' "$(date +%s)" > ~/.raven/update_check.json

Related Issues

N/A

@0xKT 0xKT left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review of ce56583, merged onto main 16719d0: clean merge, full unit suite green locally (4621 passed). Note the green checks on this PR predate #224, which switched CI to the full unit suite, so a rebase and re-run is needed regardless.

Items are ID severity. Each is independent; reply with the ID when addressed.

B1 blocking: unit tests now hit the GitHub API and write the real $HOME

  • Where: raven/cli/tui_commands.py:997-999, tests/test_cli_tui_commands.py:505
  • What: test_tui_announces_log_path_only_on_abnormal_exit (6 cases) mocks find_node, so control flow reaches maybe_refresh_async() with nothing stubbing it. Under a throwaway HOME those cases write $HOME/.raven/update_check.json with a real fetched latest_version. All failures are swallowed, so the test stays green either way. Since #224 the CI unit job runs the full suite.
  • Fix: autouse fixture redirecting update_notice._CACHE_PATH to tmp_path, mirroring tests/conftest.py:30-52 for model_catalog_cache._CACHE_PATH. That also isolates tests/test_tui_rpc_session.py, whose session_create({}) reads the real cache today.
  • Do not: patch tui_commands.maybe_refresh_async. The import at tui_commands.py:997 is function-local, so there is no module attribute to replace.

B2 blocking: a valid-JSON, non-object cache file raises instead of being swallowed

  • Where: raven/cli/update_notice.py:37-40, raising at :77 and :93
  • What: _read_cache returns whatever json.loads yields with no dict check. A cache holding "0.2.0", [1] or 42 reaches cache.get(...) and raises AttributeError inside tui() (raven tui fails to launch) and inside _default_session_info (session.create fails). Both reproduced. Reaching it takes a hand-edited file, which the "To see it locally" snippet invites, and the module contract is that the hint never breaks startup.
  • Fix: isinstance(parsed, dict) guard in _read_cache.

S1 should-fix: the nudge points at a command that always fails for editable and pip installs

  • Where: raven/cli/update_notice.py:96-100 vs raven/cli/upgrade_commands.py:440-449
  • What: raven upgrade raises for editable installs and non-uv-tool installs and exits 1, but update_notice() only compares versions, so those users get a permanent warn-colored nudge to a failing command.
  • Fix: gate the notice on upgrade_commands._is_uv_tool_install() (:356), wrapped in try/except since _uv_tool_target raises UpgradeError on a malformed receipt.

S2 should-fix: the failure path never backs off, and its comment says otherwise

  • Where: raven/cli/update_notice.py:56-65
  • What: _refresh writes checked_at only on success, so while offline, rate-limited, or whenever the latest release is a draft or prerelease (_parse_release_payload raises), every launch spawns a thread and refetches. The comment at :64 claims "try again next TTL".
  • Fix: write checked_at on failure too, keeping the previous latest_version.

S3 should-fix: the cache path bypasses the cache-dir helper

  • Where: raven/cli/update_notice.py:24
  • What: hardcodes Path.home() / ".raven", so a non-default instance set via set_config_path() writes back into the default home.
  • Fix: paths.get_cache_dir() (raven/config/paths.py:37), the designated disposable-cache dir, with precedent at raven/token_wise/model_catalog_cache.py:39. It also removes the manual mkdir.

N1 minor: duplicate version parser

update_notice.py:27,30 reimplements upgrade_commands.py:21,178. The looser semantics (prefix match, return None) look deliberate, but the module already imports _fetch_latest_release from that file, so one shared lenient parser keeps a single source of truth.

N2 minor: dead and missing demo fixtures

ui-tui/src/demo/gallery.tsx:102-103 still seeds update_behind: 2 into the SessionPanel demo, dead now that the banner block is gone, while the StatusRule demos and stubGatewayFixtures.ts:32 never exercise the new nudge state.

N3 minor: no opt-out for the launch-time network call

The fetch sends the raven version in the User-Agent (upgrade_commands.py:234-239). An env-var escape hatch is conventional for update checks.

N4 minor: update_behind is a flag in a count-shaped field

Nothing on main ever populated it (stubGatewayFixtures.ts:32 is null), so renaming it to a boolean update_available is free now and not later.

Verified fine, no action: the status bar stays on one line at narrow widths (checked down to cols=44, where Yoga shrinks and truncates both slots), and the RPC server is only constructed inside the raven tui process, so the refresher and the reader always share a process.

arelchan and others added 2 commits July 28, 2026 20:32
The TUI already read update_behind / update_command out of the session init
bundle, but nothing ever filled those fields, so the hint never appeared.

Add raven/cli/update_notice.py: a cached check against the GitHub releases
API. The live call is too slow for the session-create path, so `raven tui`
refreshes ~/.raven/update_check.json in a daemon thread at most once a day
and the gateway only reads that cache while building the info bundle. Every
failure path is swallowed -- an update hint must never break startup. The
cost is that a freshly published release surfaces on the next launch.

Show it in the status bar's right slot, replacing the cwd/branch label, in
the warn color. The old banner block is removed: the banner is the first
thing on screen and an upgrade hint does not deserve that weight.

Co-authored-by: Claude (claude-opus-5) <noreply@anthropic.com>
Review follow-ups on the startup nudge.

Tests no longer touch the network or the real home: an autouse fixture
redirects the cache path and sets RAVEN_NO_UPDATE_CHECK for the suite.
Redirecting the path alone leaves the cache absent, which is exactly the
state that spawns the fetch, so six tui cases fetched a release on every
run since CI went to the full suite.

A cache file holding valid JSON that is not an object no longer raises
out of `raven tui` or session.create -- _read_cache rejects non-dicts.

The nudge is gated on an upgradable install, so editable and non-uv-tool
users stop being pointed at a command that always exits 1. A failed
refresh now stamps checked_at too, so offline, rate-limited and
draft-release launches back off for a full TTL instead of refetching
every time.

The cache moves to paths.get_cache_dir(), resolved per call so
set_config_path() is honoured, and the duplicate version parser
delegates to upgrade_commands.

Also adds a RAVEN_NO_UPDATE_CHECK opt-out, renames update_behind to the
boolean update_available it always was, and gives the demo gallery a
StatusRule nudge state in place of the dead SessionPanel seed.

Co-authored-by: Claude (claude-opus-5) <noreply@anthropic.com>
@arelchan
arelchan force-pushed the feat/tui_update_notice branch from ce56583 to 1c581e6 Compare July 28, 2026 13:26
@arelchan

Copy link
Copy Markdown
Contributor Author

All nine items addressed, rebased onto 16719d0. Thanks for catching B1 and B2 -- both were real and both are the kind of thing a swallowed-exception module hides well.

B1 fixed -- unit tests no longer fetch or write the real home

tests/conftest.py gains an autouse fixture next to _no_openrouter_network. It does two things: redirects the cache path to tmp_path, and sets the new RAVEN_NO_UPDATE_CHECK=1 for the whole suite.

The redirect alone is not enough. Under a temp path the cache is absent, which is exactly the state that spawns the fetch thread, so the six test_tui_announces_log_path_only_on_abnormal_exit cases would still hit the GitHub API on every CI run. Opting out by env kills the fetch and the write.

tests/test_cli_update_notice.py is the one place that clears the flag, so the module is still covered end to end. And as you noted, tui_commands.maybe_refresh_async is a function-local import with no module attribute to patch -- nothing does that.

B2 fixed -- non-object cache no longer raises

isinstance(parsed, dict) guard in _read_cache. Parametrized over "0.2.0" / [1] / 42 / null, asserting both update_notice() and maybe_refresh_async() stay quiet.

S1 fixed -- no nudge when raven upgrade cannot work

update_notice() now gates on upgrade_commands._is_uv_tool_install() behind try/except, so a malformed uv receipt reads as "not upgradable" rather than propagating UpgradeError. Verified on this checkout, which is an editable install: cache says 9.9.9, notice is None.

S2 fixed -- the failure path backs off

_refresh now stamps checked_at on failure too, keeping the previous latest_version. So offline, rate-limited and draft/prerelease all back off for a full TTL instead of refetching every launch. The comment that claimed this is now true. Covered by a test that makes the fetch raise and asserts the version survives and checked_at moves.

S3 fixed -- cache lives in the cache dir

paths.get_cache_dir() / "update_check.json", resolved per call rather than at import so set_config_path() is honoured. The manual mkdir is gone with it.

N1 fixed -- one version parser

update_notice._version_key now delegates to upgrade_commands._version_key and converts its UpgradeError into None. The lenient semantics are kept where they belong (a version we cannot read means no notice), with a single grammar.

N2 fixed -- demo fixtures

Removed the dead update_behind seed from the SessionPanel demo and added a StatusRule -- update available demo so the right-slot takeover is visible in the gallery.

N3 fixed -- opt-out

RAVEN_NO_UPDATE_CHECK=1 (also accepts true / yes / on) skips both the fetch and the hint. Documented in the module docstring.

N4 fixed -- flag-shaped field

update_behind?: number | null is now update_available?: boolean | null across session.py, types.ts, appLayout.tsx, the demo gallery, stubGatewayFixtures.ts and branding.test.tsx. Nothing on main populated the old name, so this was free.

Verification

uv run ruff check raven/ tests/            # All checks passed
uv run ruff format --check raven/ tests/   # 763 files already formatted
uv run pytest tests/ --ignore=tests/integration   # 4629 passed
cd ui-tui && npm run type-check            # clean
cd ui-tui && npm test                      # 904 passed | 3 skipped
cd ui-tui && npm run lint                  # 0 errors
cd ui-tui && npx prettier --check "src/**/*.{ts,tsx}"   # clean

A first full-suite run showed two failures; a clean re-run of the same tree came back 4629 passed, and both are
pre-existing and unrelated -- the first also reproduces with this branch's changes stashed:

  • tests/test_cli_theme.py::test_bold_accent_renders_styled_not_bare fails whenever TERM and COLORTERM are unset, which is the case in a non-tty shell: rich falls back to the ANSI palette and emits \x1b[1;93m instead of truecolor. Passes with COLORTERM=truecolor, and the whole file passes standalone under that. Probably worth pinning the console's color_system in the test rather than inheriting the environment.
  • tests/test_default_context_engine.py::TestTwoTrackConcurrency::test_skill_and_memory_run_concurrently asserts elapsed < 0.15 for two 0.10s stubs. It flaked once under full-suite CPU load and passes standalone (20/20).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants