Skip to content

feat(web): add update-available banner to sidebar footer#31316

Closed
Jasonnnz wants to merge 6 commits into
mainfrom
devin/1779301842-update-available-sidebar-banner
Closed

feat(web): add update-available banner to sidebar footer#31316
Jasonnnz wants to merge 6 commits into
mainfrom
devin/1779301842-update-available-sidebar-banner

Conversation

@Jasonnnz
Copy link
Copy Markdown
Contributor

@Jasonnnz Jasonnnz commented May 20, 2026

Adds a sidebar footer banner that appears when a newer assistant release is available. The banner shows the assistant avatar, version label ("New version — 0.x.x"), and two actions: "Upgrade now" (triggers the upgrade API with polling) and "Upgrade later" (dismisses). Dismissal is version-scoped via localStorage so the banner reappears for future releases.



Open in Devin Review

Show a banner in the sidebar footer when a newer assistant release is
available. The banner displays the assistant avatar, the new version
number, and Upgrade now / Upgrade later buttons. Dismissal is persisted
per-version in localStorage so the banner reappears only for new
releases.

Wired into ChatLayout via the existing footerBanner prop on
AssistantSideMenu.

Co-Authored-By: Jason Zhou <jasonczhou3@gmail.com>
@devin-ai-integration
Copy link
Copy Markdown
Contributor

🤖 Devin AI Engineer

I'll be helping with this pull request! Here's what you should know:

✅ I will automatically:

  • Address comments on this PR. Add '(aside)' to your comment to have me ignore it.
  • Look at CI failures and help fix them

Note: I can only respond to comments from users who have write access to this repository.

⚙️ Control Options:

  • Disable automatic comment and CI monitoring

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 290d9bfb49

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +325 to +327
lifecycle.assistantId ? (
<UpdateAvailableSidebarEntry assistantId={lifecycle.assistantId} />
) : null,
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Gate footer banner prop on actual visibility

updateBanner is always a truthy React element whenever assistantId exists, even when UpdateAvailableSidebarEntry renders null. AssistantSideMenu uses footerBanner truthiness to decide whether to render SideMenu.Footer and its separator, so users with no update available still get an empty footer/separator artifact. This regresses sidebar layout for the common case and should be gated by actual banner visibility before passing the prop.

Useful? React with 👍 / 👎.

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.

Fixed in 45d39b5. Extracted useIsUpdateBannerVisible() hook that ChatLayout uses to gate the footerBanner prop. When no update is available (or it's been dismissed via localStorage), updateBanner is undefined (falsy), so the sidebar's footerBanner || footerAction check correctly skips rendering SideMenu.Footer.

Additionally, the component now calls onVisibilityChange(false) when dismissed, so ChatLayout sets updateBannerDismissed = true and immediately clears the prop.

Comment on lines +160 to +163
setDismissed(true);
}, [latestVersion]);

if (!upgradeAvailable || dismissed || isDismissedForVersion) {
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Reset dismiss state when latest version changes

The component sets dismissed to true on any dismiss action and then hard-blocks rendering via dismissed || isDismissedForVersion. Because dismissed is not keyed to latestVersion, a newly released version discovered later in the same session remains hidden until remount/reload, which breaks the intended per-version dismissal behavior. Visibility should be derived from the versioned record (or dismissed should be reset when latestVersion changes).

Useful? React with 👍 / 👎.

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.

Fixed in 45d39b5. Three changes:

  1. Dismiss reset: Added useEffect that resets dismissed to false when latestVersion changes, so a new release during the same session re-shows the banner.
  2. Footer visibility gate: Extracted useIsUpdateBannerVisible() hook used by ChatLayout to only pass the footerBanner prop when an update is actually available and not dismissed. This prevents the empty SideMenu.Footer + separator artifact.
  3. onVisibilityChange callback: The component notifies the parent when it hides (e.g. user clicks dismiss), so ChatLayout immediately clears the prop rather than waiting for a re-render cycle.

Copy link
Copy Markdown
Contributor

@devin-ai-integration devin-ai-integration Bot left a comment

Choose a reason for hiding this comment

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

Devin Review found 2 potential issues.

View 4 additional findings in Devin Review.

Open in Devin Review

assistantId,
}: UpdateAvailableSidebarEntryProps) {
const queryClient = useQueryClient();
const [dismissed, setDismissed] = useState(false);
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.

🟡 dismissed state is never reset when latestVersion changes, hiding banner for genuinely new releases

The dismissed state at update-available-sidebar-entry.tsx:62 is a simple boolean initialized to false and set to true in handleDismiss (update-available-sidebar-entry.tsx:160), but it is never reset back to false when latestVersion changes. The guard at line 163 checks !upgradeAvailable || dismissed || isDismissedForVersion — so once dismissed is true, it permanently suppresses the banner for the lifetime of the component, even if a newer release ships.

Scenario: user dismisses the v2.0.0 banner → dismissed = true, localStorage records "2.0.0". Later, v3.0.0 is released and React Query refetches. isDismissedForVersion correctly recalculates to false (localStorage has "2.0.0" ≠ "3.0.0"), but dismissed is still true, so the component returns null. The banner for v3.0.0 won't appear until the component remounts (page reload or navigation).

Suggested change
const [dismissed, setDismissed] = useState(false);
const [dismissedVersion, setDismissedVersion] = useState<string | null>(null);
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

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.

Fixed in 45d39b5. Added useEffect(() => setDismissed(false), [latestVersion]) so the in-memory dismiss state resets when a new version arrives. Combined with the isDismissedForVersion check (which reads the versioned localStorage record), a newer release will correctly re-show the banner.

Comment on lines +86 to +92
useQuery({
...assistantsRetrieveOptions({ path: { id: assistantId } }),
refetchInterval: isPollingUpgrade
? (query) =>
pollRefetchInterval(query.state.data?.current_release_version)
: false,
});
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.

🚩 No timeout on upgrade polling — indefinite refetch if backend upgrade silently fails

The polling at update-available-sidebar-entry.tsx:86-92 refetches every 3 seconds while isPollingUpgrade is true, stopping only when the assistant's current_release_version matches targetVersionRef.current. If the backend upgrade fails silently (no error thrown, version never changes), polling continues indefinitely. Consider adding a max poll count or timeout (e.g., 60 seconds) to set isPollingUpgrade = false and show an error toast. The X dismiss button is still clickable during upgrade and will hide the banner (but hooks continue running), so the user has a manual escape hatch, but the stale polling query would persist until unmount.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

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.

Fixed in 45d39b5. Added a 60-second timeout (POLL_TIMEOUT_MS). If the version hasn't updated within that window, polling stops and a toast notifies the user: "Update is taking longer than expected. Please check Settings." The pollStartedAtRef is set when polling begins and checked each interval cycle.

devin-ai-integration Bot and others added 3 commits May 20, 2026 18:43
… poll timeout

Co-Authored-By: Jason Zhou <jasonczhou3@gmail.com>
Adds `truncate` class to the version text so long pre-release version
strings (e.g. 0.8.3-local.20260520144955.4...) are clipped with an
ellipsis instead of overflowing the sidebar. A `title` attribute
preserves the full version on hover.

Co-Authored-By: Jason Zhou <jasonczhou3@gmail.com>
…w divs

Top row: avatar + title + X dismiss button
Bottom row: Upgrade now + Upgrade later buttons
Parent container uses flex-direction column

Co-Authored-By: Jason Zhou <jasonczhou3@gmail.com>
@devin-ai-integration
Copy link
Copy Markdown
Contributor

Test Results — Update Available Sidebar Banner

Ran the web app locally (Vite dev server + Playwright CDP route interception) against the PR branch. All API endpoints mocked to simulate version mismatch, same-version, and upgrade flows.

Devin session

Test Results (6/6 passed)
  • It should show the update banner with correct flex-col layout — passed
    • Parent: flex flex-col, top row: avatar + title + X, bottom row: buttons
    • Title: "New version — 0.2.0", title attr shows full version on hover
  • It should dismiss the banner when "Upgrade later" is clicked — passed
    • Banner removed from DOM, localStorage has {"version":"0.2.0","dismissedAt":...}
  • It should persist dismissal across page refresh — passed
    • F5 refresh: banner stays hidden, no empty footer artifact
  • It should NOT show the banner when on the latest version — passed
    • current_release_version = "0.2.0" = latest: no banner, no footer artifact
  • It should dismiss the banner when X button is clicked — passed
    • Banner removed, localStorage updated
  • It should trigger upgrade API when "Upgrade now" is clicked — passed
    • Upgrade API called (confirmed via Playwright console), banner auto-hid after poll
Screenshots
Banner visible (flex-col layout) Zoomed detail
banner zoomed
After dismiss + refresh (no banner) No banner when on latest version
after dismiss no update
Escalations
  • Toast notification: Success toast after "Upgrade now" may have auto-dismissed before capture. Core behavior (API called, banner hidden) confirmed.
  • Platform PR fix: keep Gmail distinct from Mail in APP_CANONICAL_MAP #7460: Not tested locally in this session — mirrors this implementation identically. CI green.

devin-ai-integration Bot and others added 2 commits May 20, 2026 19:50
…lex-1

Co-Authored-By: Jason Zhou <jasonczhou3@gmail.com>
…ction)

Co-Authored-By: Jason Zhou <jasonczhou3@gmail.com>
@Jasonnnz Jasonnnz closed this May 27, 2026
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