feat(desktop): in-app prompt to restart for an update
OpenSSF Scorecard / Scorecard analysis (push) Waiting to run
CI / Build & test (Node 20) (push) Canceled after 0s
CI / Build & test (Node 24) (push) Canceled after 0s
CI / Build & test (push) Canceled after 0s
CodeQL / Analyze (javascript-typescript) (push) Canceled after 0s
Security / Secret scan (gitleaks) (push) Canceled after 0s
Security / Dependency scan (OSV-Scanner) (push) Canceled after 0s
Security / IaC/config scan (Trivy) (push) Canceled after 0s
Security / License compliance scan (Trivy) (push) Canceled after 0s
OpenSSF Scorecard / Scorecard analysis (push) Waiting to run
CI / Build & test (Node 20) (push) Canceled after 0s
CI / Build & test (Node 24) (push) Canceled after 0s
CI / Build & test (push) Canceled after 0s
CodeQL / Analyze (javascript-typescript) (push) Canceled after 0s
Security / Secret scan (gitleaks) (push) Canceled after 0s
Security / Dependency scan (OSV-Scanner) (push) Canceled after 0s
Security / IaC/config scan (Trivy) (push) Canceled after 0s
Security / License compliance scan (Trivy) (push) Canceled after 0s
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 <noreply@anthropic.com>
This commit is contained in:
@@ -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). */}
|
||||
<SoundController />
|
||||
<UpdateBanner />
|
||||
<GlobalAudioRenderer />
|
||||
<NotificationController />
|
||||
<UpdateToast />
|
||||
|
||||
@@ -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<string | null>(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 (
|
||||
<div className="fixed bottom-4 right-4 z-[400] max-w-[320px] glass rounded-xl shadow-xl p-3.5 animate-slide-up">
|
||||
<div className="text-[13px] font-semibold text-txt-primary">{t('update.ready')}</div>
|
||||
<p className="text-[12px] text-txt-secondary mt-0.5">
|
||||
{t('update.readyVersion', { version })}
|
||||
</p>
|
||||
<div className="flex gap-2 mt-3">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => window.backspace?.installUpdate?.()}
|
||||
className="px-3 py-1.5 rounded-md text-[12px] font-medium bg-accent-primary text-white hover:brightness-110"
|
||||
>
|
||||
{t('update.restart')}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setDismissed(true)}
|
||||
className="px-3 py-1.5 rounded-md text-[12px] font-medium bg-interactive-muted text-txt-secondary hover:text-txt-primary"
|
||||
>
|
||||
{t('update.later')}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -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',
|
||||
|
||||
@@ -55,6 +55,12 @@ export const ptBR: Partial<Dictionary> = {
|
||||
'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',
|
||||
|
||||
Reference in New Issue
Block a user