From 71d2109b2f1de4bb6ca7bb616be698615d1a2df9 Mon Sep 17 00:00:00 2001 From: devsyncwrld Date: Tue, 1 Sep 2026 18:31:59 -0300 Subject: [PATCH] feat(desktop): in-app prompt to restart for an update MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The main process already showed a system notification when an update finished downloading, and nothing in the renderer listened — so the only cue vanished on its own and was missed by anyone not watching the desktop at that moment. A prompt now stays in the window until it is acted on or dismissed, with the restart wired to the install-update channel the preload already exposed. Dismissing is 'not now', not 'never': a later version reopens the prompt, since the version it refers to has changed. Co-Authored-By: Claude Opus 5 --- .../web/src/components/layout/AppLayout.tsx | 2 + .../web/src/components/ui/UpdateBanner.tsx | 53 +++++++++++++++++++ packages/web/src/i18n/locales/en.ts | 6 +++ packages/web/src/i18n/locales/pt-BR.ts | 6 +++ 4 files changed, 67 insertions(+) create mode 100644 packages/web/src/components/ui/UpdateBanner.tsx diff --git a/packages/web/src/components/layout/AppLayout.tsx b/packages/web/src/components/layout/AppLayout.tsx index e9263c80..ff82fa6e 100644 --- a/packages/web/src/components/layout/AppLayout.tsx +++ b/packages/web/src/components/layout/AppLayout.tsx @@ -1,4 +1,5 @@ import React, { useEffect } from 'react'; +import { UpdateBanner } from '../ui/UpdateBanner'; import { useExpressionStore } from '../../stores/expressionStore'; import { useParams, useNavigate } from 'react-router-dom'; import { SpaceSidebar } from './SpaceSidebar'; @@ -428,6 +429,7 @@ export function AppLayout() { (e.g. immediately after Join, or after popping voice-full back to the root). */} + diff --git a/packages/web/src/components/ui/UpdateBanner.tsx b/packages/web/src/components/ui/UpdateBanner.tsx new file mode 100644 index 00000000..37ec960b --- /dev/null +++ b/packages/web/src/components/ui/UpdateBanner.tsx @@ -0,0 +1,53 @@ +import { useEffect, useState } from 'react'; +import { isElectron } from '../../platform/platform'; +import { useT } from '../../i18n'; + +/** + * Aviso de que uma atualização já baixou e só falta reiniciar. + * + * O processo principal já mostrava uma notificação do sistema, que some sozinha + * e passa despercebida se a pessoa não estiver olhando. Este aviso fica na + * janela até ser atendido ou dispensado. + */ +export function UpdateBanner() { + const t = useT(); + const [version, setVersion] = useState(null); + const [dismissed, setDismissed] = useState(false); + + useEffect(() => { + if (!isElectron() || !window.backspace?.onUpdateDownloaded) return; + window.backspace.onUpdateDownloaded((info) => { + setVersion(info.version); + // Uma atualização nova reabre o aviso mesmo se a anterior foi dispensada: + // dispensar significa "agora não", não "nunca mais". + setDismissed(false); + }); + }, []); + + if (!version || dismissed) return null; + + return ( +
+
{t('update.ready')}
+

+ {t('update.readyVersion', { version })} +

+
+ + +
+
+ ); +} diff --git a/packages/web/src/i18n/locales/en.ts b/packages/web/src/i18n/locales/en.ts index e14917ba..c8e8d7a3 100644 --- a/packages/web/src/i18n/locales/en.ts +++ b/packages/web/src/i18n/locales/en.ts @@ -56,6 +56,12 @@ export const en = { 'accountMenu.copyId': 'Copy User ID', 'accountMenu.copied': 'Copied', + // Desktop update + 'update.ready': 'Update ready', + 'update.readyVersion': 'Version {version} is ready to install.', + 'update.restart': 'Restart to update', + 'update.later': 'Later', + // Custom emojis and stickers 'expressions.title': 'Emojis & Stickers', 'expressions.emojis': 'Emojis', diff --git a/packages/web/src/i18n/locales/pt-BR.ts b/packages/web/src/i18n/locales/pt-BR.ts index 9cfeab21..c1fa8ca2 100644 --- a/packages/web/src/i18n/locales/pt-BR.ts +++ b/packages/web/src/i18n/locales/pt-BR.ts @@ -55,6 +55,12 @@ export const ptBR: Partial = { 'accountMenu.copyId': 'Copiar ID do usuário', 'accountMenu.copied': 'Copiado', + // Atualização do app + 'update.ready': 'Atualização pronta', + 'update.readyVersion': 'A versão {version} está pronta para instalar.', + 'update.restart': 'Reiniciar para atualizar', + 'update.later': 'Depois', + // Emojis e figurinhas 'expressions.title': 'Emojis e figurinhas', 'expressions.emojis': 'Emojis',