Skip to content

feat(ui): land the background update scheduler + one-click install button#101

Merged
eliotlim merged 2 commits into
mainfrom
feat/updates-finish
Jul 5, 2026
Merged

feat(ui): land the background update scheduler + one-click install button#101
eliotlim merged 2 commits into
mainfrom
feat/updates-finish

Conversation

@eliotlim

@eliotlim eliotlim commented Jul 5, 2026

Copy link
Copy Markdown
Owner

Problem

main already ships the desktop updater runtime + signing + install plumbing (PRs #84/#85), but the background scheduler (#86) never reached main due to a merge-ordering quirk, and there was no install affordance in Settings. Net: the app can check for updates but nothing drives auto-download, and an available update dead-ends at a text label — it never installs and there's no one-click action. Board: Epic OB-338 (auto-updates) + the install-button task.

Solution

Re-land two already-reviewed pieces onto current main:

  • Background scheduler (originally feat(ui): background update scheduler + update-flow UX #86 / OB-344): updateScheduler.ts (cadence daily>24h / weekly>7d / never; ±10% jitter; security-only gates installs not checks; new major = announce-only, never auto-installed), updateRunner.ts (single-flight check+install shared with Settings), UpdateScheduler.tsx mounted in DefaultLayout (launch tick + 15-min interval; silent background failures), persistent "Restart to update" toast for security updates, and a durable "OpenBook N.x is available" line in Settings. Code (Quinn) + design (Devon) previously cleared, all advisories folded.
  • One-click Install button (Settings → Updates): shown on update-available, runs runDownloadAndInstall → save-guarded relaunch(), with progress + error + a11y states; additive to the scheduler/toast. Design (Devon) + code (Quinn) previously cleared.

Before / After

Behavior Before (main) After
Auto update check per cadence none (no scheduler) daily/weekly/never scheduler drives it
Available update dead-ends at a text label auto-downloads + "Restart to update" toast (security: persistent)
Install from Settings no affordance one-click "Install & restart" button
New major version (no signal) durable "N.x available" line + once-per-major toast, never auto-installed

Test procedure

  • pnpm verify green (report counts).
  • pnpm --filter @book.dev/web test:e2e update-scheduler.spec.ts — scheduler + install-button e2e via the ?updates= seam (absence→presence→install/relaunch counters; cadence gating; security persistent toast).

Operational notes

Footer: verify green (counts above) + web e2e; re-landing code that previously cleared code (Quinn) + design (Devon) gates — no new review surface.

eliotlim and others added 2 commits July 5, 2026 16:19
* feat(ui): single-flight update pipeline, sticky toasts, save-pending probe

Groundwork for the background update scheduler (OB-344):
- lib/updateRunner: module-singleton single-flight for checkForUpdate and
  downloadAndInstall, shared by Settings check-now and the scheduler so the
  two paths can never double-run; stamps lastCheckAt on every attempt and
  lastCheckSuccessAt only on completion (policy moves here from the section).
- UpdatesSection: check-now goes through the runner; inline result behavior
  unchanged (error keeps the old 'Last checked', success refreshes it).
- toast: durationMs Infinity = persistent toast (no auto-dismiss timer) for
  the staged-security-update case.
- pageSaveStatus.anyPageSavePending(): 'is a restart risky right now?' probe.
- updateCheck.semverMajor(): major extraction for the never-cross-majors rule.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* feat(ui): background update scheduler — cadence, jitter, decision logic, update-flow UX

The final build item of the auto-updates epic (OB-344):
- lib/updateScheduler: pure decision module. isBackgroundCheckDue (daily>24h,
  weekly>7d, never=zero requests; ±10% jitter against release-day thundering
  herd; future lastCheckAt = clock skew = stale) and decideUpdateAction
  (security-only gates installs, not checks; a newer major is NEVER installed —
  informational toast at most once per major, persisted in
  updates.majorAnnounced; majors announce even from an up-to-date result).
- components/UpdateScheduler: host mounted in DefaultLayout (PluginBoot
  pattern — inside ConfirmProvider, next to ToastHost, so both shells get it
  with zero per-shell wiring; inert without platform.updates). Launch tick +
  15min interval; acts via the shared runner; download then toast 'Update
  ready — Restart to update' (persistent for security fixes); background
  failures are console.debug-silent, retried next due tick.
- Restart action relaunches directly (autosave flushes edits); a confirm
  dialog guards only the risky case where a page save is mid-flight/failed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* test(ui,web): update-scheduler decision matrix, staleness math, runner + host units, e2e

- updateScheduler.test: exact 30-cell table {daily,weekly,never} ×
  {securityOnly on/off} × {no update, minor, security, new major, error} →
  {no-op, install-update, install-security, announce-major}; staleness incl.
  clock-skew and jitter bounds; once-per-major persistence.
- updateRunner.test: single-flight for check + install, attempt-vs-success
  stamping, never-rejects, rejection propagation.
- UpdateScheduler.test: host-level matrix with a mocked platform — never =
  zero calls, security-only filtering, persistent security toast whose action
  relaunches, silent error/download failures, once-per-major announce.
- web e2e (update-scheduler.spec): via the ?updates= seam, which now counts
  check/install/relaunch calls on window — security-only ON + non-security
  update → no toast/install; security update → persistent toast (outlives the
  7s auto-dismiss) whose action relaunches; cadence never → zero checks.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* feat(ui,web): durable major-availability surface + update-flow polish (review advisories)

Design-gate advisories on the update scheduler (OB-344):
- M1: the once-per-major toast was the ONLY major signal (~7s, no action;
  Settings read 'Up to date' when the current line is current). The shared
  runner now records updates.latestMajorSeen on every successful check
  (clearing it when none is reported, so a stale '2.x is available' can't
  outlive an upgrade), and the Updates section renders it as a persistent
  informational line — 'OpenBook {major}.x is available' — between checks
  and across sessions. Toast behavior unchanged (still once per major).
- L1: the restart-while-saves-pending confirm is destructive:true
  (data-loss class, matches the trash idiom).
- L2: confirm body reworded to 'Some changes haven't been saved yet' —
  anyPageSavePending() also matches save-FAILED, which 'still saving' misled.
- L3: ?updates=major seam mode (up-to-date + latestMajor, the shape the check
  API produces for a major-only bump) so the major path is observable in
  browser/e2e/Chromatic; new e2e covers toast-once + durable Settings line
  surviving a reload with no re-toast.

Tests: +5 unit (runner records/clears/preserves latestMajorSeen; persistence
round-trip), +1 host assertion, +1 e2e. updates.majorAvailableToast renamed
majorAvailable (now shared by toast + Settings line).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Add an "Install & restart" action to UpdatesSection, shown when a manual
check reports status 'update-available'. It runs the existing single-flight
install pipeline — runDownloadAndInstall(updates) then updates.relaunch() —
so a background download in flight is joined, never doubled.

- Progress: button disabled + aria-busy with "Downloading…" (stage) then
  "Installing…" (relaunch) phase labels; spinner icon while in progress.
- Error: a role="alert" inline surface on download/verify/relaunch failure;
  cleared on the next check. The check status span keeps its aria-live.
- Restart guard: reuses the UpdateScheduler.restart() idiom — confirm via
  restartConfirm* i18n only when anyPageSavePending().
- Desktop-only (gated on platform.updates); scheduler + apply-on-restart
  toast flow untouched (additive).
- i18n: updates.installAction/downloading/installing/installError (en).
- SettingsPanel unit test now wraps ConfirmProvider (UpdatesSection consumes
  useConfirm, as sibling settings tabs already do).
- e2e: update-scheduler.spec asserts the button appears post-check and one
  click drives install+relaunch via the web-stub counters.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@vercel

vercel Bot commented Jul 5, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
app.book.pub Ready Ready Preview, Comment Jul 5, 2026 8:39am

Request Review

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.

1 participant