buffr — update channel

A version-check + manual-update flow. No automatic binary replacement. Real auto-update needs signing infrastructure (Apple Developer ID + notarization on macOS, Authenticode on Windows, a signing service we don't have yet) so it's deferred to post-1.0. What ships today:

  1. Once per [updates] check_interval_hours (default 24 h), buffr makes one HTTP GET against the GitHub releases API: https://api.github.com/repos/{repo}/releases/latest.
  2. The result is cached at <data>/update-cache.json.
  3. The statusline reads the cache on launch; if a newer release exists it shows * upd. If the cache is older than check_interval_hours it shows * upd? (stale — we don't know if it's still current).
  4. The user runs buffr --check-for-updates to refresh manually. There is no in-chrome "update now" button (no signed binary swap to trigger).

CLI

buffr --check-for-updates    # hits the network, prints status, exits 0
buffr --update-status        # reads cache, prints status, exits 0

Output format:

up-to-date     <current_version>
available      <current>  <latest>  <tag>  <html_url>
stale          <last_checked_rfc3339>  <latest>  <tag>  <html_url>
disabled
error          <message>

Config

[updates]
# Master switch. When false, buffr makes ZERO network calls — the
# `--check-for-updates` flag short-circuits to "disabled" without
# touching the network. The statusline indicator never appears.
enabled = true

# How often `--check-for-updates` is allowed to actually hit GitHub.
# Reads inside the window are served from the disk cache. Minimum 1.
check_interval_hours = 24

# `owner/repo` slug. Forks point this at their own repo.
github_repo = "kryptic-sh/buffr"

What gets sent

A single GET to a public REST endpoint. The request carries no PII:

  • Path: /repos/{repo}/releases/latest
  • Headers: User-Agent: buffr/<version> (mandatory — GitHub rejects user-agent-less requests) and Accept: application/vnd.github+json.
  • No cookies, no auth token, no telemetry payload.

GitHub logs the request like any other API request (IP + timestamp). buffr does not receive that log; we do not run our own collector.

Dismissing a release — API only, not reachable yet

UpdateChecker::dismiss(version) records a release in the cache as "ignored". Subsequent check_cached/check_now for the same version resolve to UpToDate instead of Available. Filtering happens at read time, not write time: the cache stays the source of truth for "what GitHub last reported".

Not user-facing. Nothing outside updates.rs and its unit tests calls dismiss() — there is no CLI flag and no chrome affordance for it, so a user cannot currently dismiss a release. Both the dismiss entry point and a --reset-update-dismissals flag to clear the list are outstanding work.

Implementation

  • crates/buffr-core/src/updates.rsUpdateChecker, UpdateStatus, HttpClient trait, UreqClient impl.
  • crates/buffr-config/src/lib.rs[updates] section schema + validation (repo shape, non-zero interval).
  • apps/buffr-app/src/main.rs--check-for-updates / --update-status CLI short-circuits. Statusline * upd indicator wired to the cache read. (The buffr supervisor takes only --heartbeat-timeout / --heartbeat-disable and forwards everything else to buffr-app, so typing buffr --check-for-updates still works.)

The trait HttpClient exists so unit tests can drive the state machine without touching the real network. The real network path uses ureq 3.x (Agent::config_builder()) with a 5 s connect + 5 s read timeout.