feat(update): show "new version available" modal with one-click update [ENG-1686]#408
feat(update): show "new version available" modal with one-click update [ENG-1686]#408robinnewhouse wants to merge 7 commits intomainfrom
Conversation
…e (ENG-1686) Detect newer Kanban versions on startup and surface a modal in the web UI that lets the user run the appropriate install command for their package manager (npm, pnpm, yarn, bun, npx) without leaving the browser. Backend - src/update/update.ts: track PendingUpdateNotification (current/latest version, update timing, user-facing install command) when the existing startup auto-update check detects a newer version. Add buildUserFacingInstallCommand to derive the command per package manager. - src/core/api-contract.ts: add runtimeUpdateStatusResponseSchema and runtimeRunUpdateResponseSchema. - src/trpc/runtime-api.ts + src/trpc/app-router.ts: expose runtime.getUpdateStatus query and runtime.runUpdateNow mutation. - src/server/runtime-server.ts + src/cli.ts: thread getUpdateStatus and runUpdateNow as deps. runUpdateNow delegates to the existing runOnDemandUpdate so users get the same install behavior as `kanban update`. Frontend - web-ui/src/runtime/runtime-config-query.ts: add fetchRuntimeUpdateStatus and runRuntimeUpdateNow tRPC client helpers. - web-ui/src/hooks/use-update-notification.ts: new hook that polls the runtime once on mount, retries after 10s (the server populates the pending notification asynchronously), then falls back to an hourly poll. Returns the available update info plus a session-scoped dismiss(). - web-ui/src/components/update-available-dialog.tsx: new centered modal with the install command in a copy-able code block, a Later/Update Now button row, and idle/running/success/error phases. Update Now calls runtime.runUpdateNow and shows the result inline; Later dismisses for the session. - web-ui/src/App.tsx: wire the hook and render the modal. Tests - test/runtime/update/auto-update.test.ts: extend the getPendingUpdateNotification suite to assert installCommand for the startup (npm install -g) and shutdown (npx) timing paths.
Greptile SummaryThis PR adds a "New version available" modal to the web UI that surfaces the existing backend startup auto-update check ( Confidence Score: 5/5Safe to merge — all three previously-flagged bugs have been correctly addressed and the new code is well-tested. No P0/P1 findings remain. The three issues from the prior review round (notification not cleared post-update, hook not self-correcting, wrong command for dlx/bunx users) are all fixed. Backend state management is correct (module-level singleton guarded by clear function), frontend polling is clean (cancelled flag + ref-based dismiss), and the dialog's allowClose guard properly prevents escape during an in-flight update. No files require special attention.
|
| Filename | Overview |
|---|---|
| src/update/update.ts | Adds PendingUpdateNotification, buildUserFacingInstallCommand (with updateTiming-aware dlx/bunx handling), getPendingUpdateNotification, and clearPendingUpdateNotification; sets the notification in runAutoUpdateCheck before spawning the actual update. |
| src/cli.ts | Wires getUpdateStatus and runUpdateNow into the runtime server deps; runUpdateNow correctly calls clearPendingUpdateNotification() on updated/already_up_to_date/cache_refreshed to prevent modal re-appearance after a successful update. |
| web-ui/src/hooks/use-update-notification.ts | Polling hook with mount check, 10s early retry, hourly interval; correctly self-corrects to null when a subsequent poll returns updateAvailable: false; dismiss is session-scoped via a ref. |
| web-ui/src/components/update-available-dialog.tsx | Clean four-phase (idle/running/success/error) dialog; allowClose guard prevents escape while updating; error state is retryable; success shows correct restart guidance. |
| test/runtime/update/auto-update.test.ts | New getPendingUpdateNotification suite covers startup/shutdown timing paths for npm, npx, pnpm dlx, yarn dlx, and bunx, plus the no-update and clearPendingUpdateNotification afterEach cleanup cases. |
Sequence Diagram
sequenceDiagram
participant S as Server (startup)
participant ST as update.ts
participant API as tRPC runtime API
participant H as useUpdateNotification
participant D as UpdateAvailableDialog
S->>ST: runAutoUpdateCheck()
ST->>ST: detect newer version
ST->>ST: set pendingUpdateNotification
H->>API: fetchRuntimeUpdateStatus() [on mount]
API->>ST: getPendingUpdateNotification()
ST-->>API: PendingUpdateNotification
API-->>H: { updateAvailable: true, ... }
H->>D: render dialog
D->>API: runRuntimeUpdateNow() [Update Now click]
API->>ST: runOnDemandUpdate()
ST-->>API: { status: updated }
API->>ST: clearPendingUpdateNotification()
API-->>D: success response
D->>D: phase → success
H->>API: fetchRuntimeUpdateStatus() [hourly poll]
API->>ST: getPendingUpdateNotification() → null
API-->>H: { updateAvailable: false }
H->>H: setAvailableUpdate(null)
Reviews (4): Last reviewed commit: "test(runtime): use update-aware api help..." | Re-trigger Greptile
…-correct hook Address Greptile review feedback on #408: - src/cli.ts: when runUpdateNow finishes with updated / already_up_to_date / cache_refreshed, call clearPendingUpdateNotification() so the modal does not reappear on page reload after the user has already applied the update. The pending notification is a one-shot signal recorded at startup; it is not meant to persist past resolution. - web-ui/src/hooks/use-update-notification.ts: when a poll returns updateAvailable: false, reset availableUpdate to null so the hook self- corrects without requiring a page reload (e.g., when another session applied the update and the runtime cleared its pending notification). - web-ui/src/hooks/use-update-notification.test.tsx: cover the happy path, up-to-date null state, the new self-correction path, dismiss(), and rejected query (5 tests).
|
Thanks for the review. Both points addressed in P1 — P2 — hook self-correction. Added |
…nly for actual updates Address Greptile review feedback on #408: - src/update/update.ts: buildUserFacingInstallCommand now branches on installation.updateTiming. PNPM/YARN/BUN with updateTiming "shutdown" (transient pnpm dlx / yarn dlx / bunx invocations) yield re-run commands (pnpm dlx kanban / yarn dlx kanban / bunx kanban) instead of steering the user toward an unintended global install. - web-ui/src/components/update-available-dialog.tsx: success state now only shows "Updated to Kanban X. Restart Kanban" when status is literally "updated". For already_up_to_date and cache_refreshed it falls back to the result.message (e.g. "Kanban is already up to date (1.0.0)."), avoiding a misleading restart prompt when nothing was installed. - test/runtime/update/auto-update.test.ts: add three new cases asserting installCommand for pnpm dlx, yarn dlx, and bunx transient runs.
|
Both points addressed in P1 — wrong install command for P2 — misleading restart message on Added 3 tests in |
| import type { BoardData } from "@/types"; | ||
|
|
||
| export default function App(): ReactElement { | ||
| const { availableUpdate, dismiss: dismissUpdateNotification } = useUpdateNotification(); |
There was a problem hiding this comment.
Generally, I think we should start to debloat the App.tsx component. We could extract all of these to a Provider or something, that takes care of fetching the hook and conditionally rendering the UpdateAvailableDialog.
WDYT?
There was a problem hiding this comment.
Always in favor of de-bloating. Let me see what I can do.
| getUpdateStatus?: () => RuntimeUpdateStatusResponse; | ||
| runUpdateNow?: () => Promise<RuntimeRunUpdateResponse>; |
There was a problem hiding this comment.
Why can these be undefined?
There was a problem hiding this comment.
Thanks for catching. fixed.
Adds a modal that pops up in the web UI when a newer Kanban version is available on npm, with a one-click "Update Now" button that runs the right install command for the user's package manager. Closes ENG-1686.
Why
Kanban is meant to run all the time, so users naturally fall behind on versions. There was already a backend startup auto-update check (
runAutoUpdateCheck) plus akanban updateCLI command (runOnDemandUpdate), but neither surfaced anything in the UI — a user with the browser tab open all day had no signal that a new version had shipped, and the--updateflag is invisible to non-CLI users.This adds the missing UI piece: when the existing startup check finds a newer version, the next page render shows a centered modal that mirrors the linked design (icon, title, current/latest version, copy-able install command, Later / Update Now). Update Now goes through the existing
runOnDemandUpdate, so behavior matcheskanban updateexactly — same package-manager detection, same fallbacks.Change
Backend
src/update/update.ts: extendrunAutoUpdateCheckto record aPendingUpdateNotification(current/latest version, update timing, user-facing install command) when a newer version is detected. AddbuildUserFacingInstallCommandso the modal showspnpm add -g …,npx kanban, etc. — not the internal cache-refresh script.src/core/api-contract.ts: addruntimeUpdateStatusResponseSchemaandruntimeRunUpdateResponseSchema.src/trpc/runtime-api.ts+src/trpc/app-router.ts: exposeruntime.getUpdateStatus(query) andruntime.runUpdateNow(mutation).src/server/runtime-server.ts+src/cli.ts: threadgetUpdateStatusandrunUpdateNowas deps.runUpdateNowdelegates to the existingrunOnDemandUpdateso users get the same install behavior askanban update.Frontend
web-ui/src/runtime/runtime-config-query.ts:fetchRuntimeUpdateStatusandrunRuntimeUpdateNowtRPC client helpers.web-ui/src/hooks/use-update-notification.ts: polls the runtime once on mount, retries after 10s (the server populates the pending notification asynchronously after startup), then falls back to an hourly poll. Returns{ availableUpdate, dismiss }and is session-scoped — dismissing won't resurface the same prompt until the next page load.web-ui/src/components/update-available-dialog.tsx: centered modal with the install command in a copy-able code block. Phases: idle / running (spinner on Update Now, Later disabled) / success (green check + "Restart Kanban to use the new version" + Close) / error (inline red alert, retryable).web-ui/src/App.tsx: wire the hook and render the modal alongside the other top-level dialogs.Tests
test/runtime/update/auto-update.test.ts: extend thegetPendingUpdateNotificationsuite to assertinstallCommandfor both startup (npm install -g …) and shutdown (npx kanban) timing paths.How to test
src/update/update.ts:npm run dev:full, open the Vite dev URL.npm install -g kanban@latestruns in the dev:full terminal, modal flips to a green success state. Click Close.nullon this branch).In production, the modal only fires when the existing startup auto-update check actually finds a newer version on npm, so end users see it organically.
Notes
kanban update. We didn't add a parallel install path; the modal just calls intorunOnDemandUpdate.runAutoUpdateCheckonly runs on startup, so polling more often wouldn't produce different results.Laterdismisses the modal for the session. The nextkanbanlaunch will show it again if the underlying version still mismatches.Screenshots