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',