mirror of
https://github.com/NanmiCoder/cc-haha
synced 2026-07-16 13:03:31 +08:00
Make desktop updates less noisy and more truthful
The desktop updater now renders release notes as markdown, avoids fake 0% progress when the server omits Content-Length, and remembers when the user has dismissed a specific release prompt so reopening the app does not nag again for the same version. Constraint: Existing 0.1.4 clients can receive updater events without total size metadata and users still need a manual update path in About Rejected: Keep repeating the prompt on every launch | creates avoidable noise after an explicit later decision Rejected: Global dismiss flag for all future releases | would hide newer versions that should prompt again Confidence: high Scope-risk: narrow Reversibility: clean Directive: Keep prompt suppression keyed to availableVersion only; About page visibility and manual update actions must remain available Tested: bun run test src/stores/updateStore.test.ts src/components/shared/UpdateChecker.test.tsx src/__tests__/generalSettings.test.tsx; bun run lint; manual local updater validation from 0.1.4 to 0.1.5 on /Applications and an extracted v0.1.4 release bundle Not-tested: Signed and notarized macOS distribution behavior outside this local machine
This commit is contained in:
parent
5a86ab097f
commit
b7aefc3d01
@ -4,6 +4,8 @@ import '@testing-library/jest-dom'
|
||||
|
||||
import { Settings } from '../pages/Settings'
|
||||
import { useSettingsStore } from '../stores/settingsStore'
|
||||
import { useUIStore } from '../stores/uiStore'
|
||||
import { useUpdateStore } from '../stores/updateStore'
|
||||
|
||||
vi.mock('../api/agents', () => ({
|
||||
agentsApi: {
|
||||
@ -69,6 +71,23 @@ describe('Settings > General tab', () => {
|
||||
useSettingsStore.setState({ skipWebFetchPreflight: enabled })
|
||||
}),
|
||||
})
|
||||
|
||||
useUIStore.setState({ pendingSettingsTab: null })
|
||||
useUpdateStore.setState({
|
||||
status: 'idle',
|
||||
availableVersion: null,
|
||||
releaseNotes: null,
|
||||
progressPercent: 0,
|
||||
downloadedBytes: 0,
|
||||
totalBytes: null,
|
||||
error: null,
|
||||
checkedAt: null,
|
||||
shouldPrompt: false,
|
||||
initialize: vi.fn().mockResolvedValue(undefined),
|
||||
checkForUpdates: vi.fn().mockResolvedValue(null),
|
||||
installUpdate: vi.fn().mockResolvedValue(undefined),
|
||||
dismissPrompt: vi.fn(),
|
||||
})
|
||||
})
|
||||
|
||||
it('shows WebFetch preflight toggle enabled by default', () => {
|
||||
@ -91,3 +110,55 @@ describe('Settings > General tab', () => {
|
||||
expect(useSettingsStore.getState().setSkipWebFetchPreflight).toHaveBeenCalledWith(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe('Settings > About tab', () => {
|
||||
beforeEach(() => {
|
||||
useUIStore.setState({ pendingSettingsTab: 'about' })
|
||||
useUpdateStore.setState({
|
||||
status: 'available',
|
||||
availableVersion: '0.1.5',
|
||||
releaseNotes: '# Claude Code Haha v0.1.5\n\n- Fixed updater rendering\n- Added markdown support',
|
||||
progressPercent: 0,
|
||||
downloadedBytes: 0,
|
||||
totalBytes: null,
|
||||
error: null,
|
||||
checkedAt: null,
|
||||
shouldPrompt: true,
|
||||
initialize: vi.fn().mockResolvedValue(undefined),
|
||||
checkForUpdates: vi.fn().mockResolvedValue(null),
|
||||
installUpdate: vi.fn().mockResolvedValue(undefined),
|
||||
dismissPrompt: vi.fn(),
|
||||
})
|
||||
})
|
||||
|
||||
it('renders release notes with markdown formatting', async () => {
|
||||
render(<Settings />)
|
||||
|
||||
expect(await screen.findByRole('heading', { name: 'Claude Code Haha v0.1.5' })).toBeInTheDocument()
|
||||
expect(screen.getByText('Fixed updater rendering')).toBeInTheDocument()
|
||||
expect(screen.getByText('Added markdown support')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('shows downloaded bytes instead of a fake zero percent when total size is unknown', async () => {
|
||||
useUpdateStore.setState({
|
||||
status: 'downloading',
|
||||
availableVersion: '0.1.5',
|
||||
releaseNotes: '# Claude Code Haha v0.1.5',
|
||||
progressPercent: 0,
|
||||
downloadedBytes: 1536,
|
||||
totalBytes: null,
|
||||
error: null,
|
||||
checkedAt: null,
|
||||
shouldPrompt: true,
|
||||
initialize: vi.fn().mockResolvedValue(undefined),
|
||||
checkForUpdates: vi.fn().mockResolvedValue(null),
|
||||
installUpdate: vi.fn().mockResolvedValue(undefined),
|
||||
dismissPrompt: vi.fn(),
|
||||
})
|
||||
|
||||
render(<Settings />)
|
||||
|
||||
expect(await screen.findByText('Downloading update... 1.5 KB downloaded')).toBeInTheDocument()
|
||||
expect(screen.queryByText('Downloading update... 0%')).not.toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
65
desktop/src/components/shared/UpdateChecker.test.tsx
Normal file
65
desktop/src/components/shared/UpdateChecker.test.tsx
Normal file
@ -0,0 +1,65 @@
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import { render, screen } from '@testing-library/react'
|
||||
import '@testing-library/jest-dom'
|
||||
|
||||
import { UpdateChecker } from './UpdateChecker'
|
||||
import { useUpdateStore } from '../../stores/updateStore'
|
||||
|
||||
describe('UpdateChecker', () => {
|
||||
beforeEach(() => {
|
||||
Object.defineProperty(window, '__TAURI__', {
|
||||
value: {},
|
||||
configurable: true,
|
||||
})
|
||||
|
||||
useUpdateStore.setState({
|
||||
status: 'available',
|
||||
availableVersion: '0.1.5',
|
||||
releaseNotes: '# Claude Code Haha v0.1.5\n\n[Release notes](https://example.com/releases/v0.1.5)',
|
||||
progressPercent: 0,
|
||||
downloadedBytes: 0,
|
||||
totalBytes: null,
|
||||
error: null,
|
||||
checkedAt: null,
|
||||
shouldPrompt: true,
|
||||
initialize: vi.fn().mockResolvedValue(undefined),
|
||||
checkForUpdates: vi.fn().mockResolvedValue(null),
|
||||
installUpdate: vi.fn().mockResolvedValue(undefined),
|
||||
dismissPrompt: vi.fn(),
|
||||
})
|
||||
})
|
||||
|
||||
it('renders markdown release notes in the update prompt', () => {
|
||||
render(<UpdateChecker />)
|
||||
|
||||
expect(screen.getByText('v0.1.5 available')).toBeInTheDocument()
|
||||
expect(screen.getByRole('heading', { name: 'Claude Code Haha v0.1.5' })).toBeInTheDocument()
|
||||
|
||||
const link = screen.getByRole('link', { name: 'Release notes' })
|
||||
expect(link).toHaveAttribute('href', 'https://example.com/releases/v0.1.5')
|
||||
expect(link).toHaveAttribute('target', '_blank')
|
||||
})
|
||||
|
||||
it('shows downloaded bytes when the updater does not provide total size', () => {
|
||||
useUpdateStore.setState({
|
||||
status: 'downloading',
|
||||
availableVersion: '0.1.5',
|
||||
releaseNotes: '# Claude Code Haha v0.1.5',
|
||||
progressPercent: 0,
|
||||
downloadedBytes: 1536,
|
||||
totalBytes: null,
|
||||
error: null,
|
||||
checkedAt: null,
|
||||
shouldPrompt: true,
|
||||
initialize: vi.fn().mockResolvedValue(undefined),
|
||||
checkForUpdates: vi.fn().mockResolvedValue(null),
|
||||
installUpdate: vi.fn().mockResolvedValue(undefined),
|
||||
dismissPrompt: vi.fn(),
|
||||
})
|
||||
|
||||
render(<UpdateChecker />)
|
||||
|
||||
expect(screen.getByText('Downloading update... 1.5 KB downloaded')).toBeInTheDocument()
|
||||
expect(screen.queryByText(/0%/)).not.toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
@ -1,7 +1,9 @@
|
||||
import { useEffect } from 'react'
|
||||
import { useTranslation } from '../../i18n'
|
||||
import { MarkdownRenderer } from '../markdown/MarkdownRenderer'
|
||||
import { isTauriRuntime } from '../../lib/desktopRuntime'
|
||||
import { useUpdateStore } from '../../stores/updateStore'
|
||||
import { formatBytes } from '../../lib/formatBytes'
|
||||
|
||||
export function UpdateChecker() {
|
||||
const t = useTranslation()
|
||||
@ -9,6 +11,8 @@ export function UpdateChecker() {
|
||||
const availableVersion = useUpdateStore((s) => s.availableVersion)
|
||||
const releaseNotes = useUpdateStore((s) => s.releaseNotes)
|
||||
const progressPercent = useUpdateStore((s) => s.progressPercent)
|
||||
const downloadedBytes = useUpdateStore((s) => s.downloadedBytes)
|
||||
const totalBytes = useUpdateStore((s) => s.totalBytes)
|
||||
const error = useUpdateStore((s) => s.error)
|
||||
const shouldPrompt = useUpdateStore((s) => s.shouldPrompt)
|
||||
const initialize = useUpdateStore((s) => s.initialize)
|
||||
@ -26,11 +30,15 @@ export function UpdateChecker() {
|
||||
|
||||
if (!showPopup) return null
|
||||
|
||||
const hasKnownProgress = typeof totalBytes === 'number' && totalBytes > 0
|
||||
const downloadedText = formatBytes(downloadedBytes)
|
||||
const statusText =
|
||||
status === 'restarting'
|
||||
? t('update.restarting')
|
||||
: status === 'downloading'
|
||||
? t('update.downloading')
|
||||
? hasKnownProgress
|
||||
? t('update.downloading')
|
||||
: t('update.progressBytes', { downloaded: downloadedText })
|
||||
: null
|
||||
|
||||
return (
|
||||
@ -41,22 +49,30 @@ export function UpdateChecker() {
|
||||
</p>
|
||||
|
||||
{releaseNotes && (
|
||||
<p className="mt-2 text-xs leading-5 text-[var(--color-text-secondary)] whitespace-pre-wrap line-clamp-5">
|
||||
{releaseNotes}
|
||||
</p>
|
||||
<div className="mt-2 max-h-40 overflow-y-auto rounded-lg border border-[var(--color-border)]/60 bg-[var(--color-surface)]/70 px-3 py-2">
|
||||
<MarkdownRenderer
|
||||
content={releaseNotes}
|
||||
className="text-xs leading-5 text-[var(--color-text-secondary)] [&_h1]:mb-2 [&_h1]:text-sm [&_h1]:font-semibold [&_h2]:mb-1.5 [&_h2]:text-xs [&_h2]:font-semibold [&_p]:my-1.5 [&_p]:text-xs [&_p]:leading-5 [&_ul]:my-1.5 [&_ol]:my-1.5"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{(status === 'downloading' || status === 'restarting') && (
|
||||
<div className="mt-3">
|
||||
<div className="h-1.5 bg-[var(--color-surface)] rounded-full overflow-hidden">
|
||||
<div
|
||||
className="h-full bg-[var(--color-text-accent)] transition-all duration-300"
|
||||
style={{ width: `${Math.min(progressPercent, 100)}%` }}
|
||||
/>
|
||||
{hasKnownProgress || status === 'restarting' ? (
|
||||
<div
|
||||
className="h-full bg-[var(--color-text-accent)] transition-all duration-300"
|
||||
style={{ width: `${Math.min(progressPercent, 100)}%` }}
|
||||
/>
|
||||
) : (
|
||||
<div className="h-full w-1/3 rounded-full bg-[var(--color-text-accent)]/75 animate-pulse" />
|
||||
)}
|
||||
</div>
|
||||
{statusText && (
|
||||
<p className="text-xs text-[var(--color-text-tertiary)] mt-1">
|
||||
{statusText} {status === 'downloading' ? `${progressPercent}%` : ''}
|
||||
{statusText}
|
||||
{status === 'downloading' && hasKnownProgress ? ` ${progressPercent}%` : ''}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@ -625,6 +625,7 @@ export const en = {
|
||||
'update.now': 'Update now',
|
||||
'update.later': 'Later',
|
||||
'update.progress': 'Downloading update... {progress}%',
|
||||
'update.progressBytes': 'Downloading update... {downloaded} downloaded',
|
||||
'update.releaseNotes': 'Release Notes',
|
||||
'update.restarting': 'Restarting to finish update...',
|
||||
'update.upToDate': 'You are up to date on v{version}.',
|
||||
|
||||
@ -627,6 +627,7 @@ export const zh: Record<TranslationKey, string> = {
|
||||
'update.now': '立即更新',
|
||||
'update.later': '稍后',
|
||||
'update.progress': '正在下载更新... {progress}%',
|
||||
'update.progressBytes': '正在下载更新... 已下载 {downloaded}',
|
||||
'update.releaseNotes': '更新说明',
|
||||
'update.restarting': '正在重启以完成更新...',
|
||||
'update.upToDate': '当前已是最新版本 v{version}。',
|
||||
|
||||
18
desktop/src/lib/formatBytes.ts
Normal file
18
desktop/src/lib/formatBytes.ts
Normal file
@ -0,0 +1,18 @@
|
||||
export function formatBytes(bytes: number): string {
|
||||
if (!Number.isFinite(bytes) || bytes <= 0) return '0 B'
|
||||
|
||||
const units = ['B', 'KB', 'MB', 'GB', 'TB']
|
||||
let value = bytes
|
||||
let unitIndex = 0
|
||||
|
||||
while (value >= 1024 && unitIndex < units.length - 1) {
|
||||
value /= 1024
|
||||
unitIndex += 1
|
||||
}
|
||||
|
||||
const maximumFractionDigits = value >= 10 || unitIndex === 0 ? 0 : 1
|
||||
|
||||
return `${new Intl.NumberFormat(undefined, {
|
||||
maximumFractionDigits,
|
||||
}).format(value)} ${units[unitIndex]}`
|
||||
}
|
||||
@ -22,6 +22,7 @@ import { ComputerUseSettings } from './ComputerUseSettings'
|
||||
import { useUIStore, type SettingsTab } from '../stores/uiStore'
|
||||
import { ClaudeOfficialLogin } from '../components/settings/ClaudeOfficialLogin'
|
||||
import { useUpdateStore } from '../stores/updateStore'
|
||||
import { formatBytes } from '../lib/formatBytes'
|
||||
|
||||
export function Settings() {
|
||||
const [activeTab, setActiveTab] = useState<SettingsTab>('providers')
|
||||
@ -1279,6 +1280,8 @@ function AboutSettings() {
|
||||
const availableVersion = useUpdateStore((s) => s.availableVersion)
|
||||
const releaseNotes = useUpdateStore((s) => s.releaseNotes)
|
||||
const progressPercent = useUpdateStore((s) => s.progressPercent)
|
||||
const downloadedBytes = useUpdateStore((s) => s.downloadedBytes)
|
||||
const totalBytes = useUpdateStore((s) => s.totalBytes)
|
||||
const error = useUpdateStore((s) => s.error)
|
||||
const checkedAt = useUpdateStore((s) => s.checkedAt)
|
||||
const checkForUpdates = useUpdateStore((s) => s.checkForUpdates)
|
||||
@ -1307,11 +1310,15 @@ function AboutSettings() {
|
||||
})
|
||||
: null
|
||||
|
||||
const hasKnownProgress = typeof totalBytes === 'number' && totalBytes > 0
|
||||
const downloadedText = formatBytes(downloadedBytes)
|
||||
const updateDescription =
|
||||
updateStatus === 'checking'
|
||||
? t('update.checking')
|
||||
: updateStatus === 'downloading'
|
||||
? t('update.progress', { progress: String(progressPercent) })
|
||||
? hasKnownProgress
|
||||
? t('update.progress', { progress: String(progressPercent) })
|
||||
: t('update.progressBytes', { downloaded: downloadedText })
|
||||
: updateStatus === 'restarting'
|
||||
? t('update.restarting')
|
||||
: updateStatus === 'available' && availableVersion
|
||||
@ -1400,22 +1407,33 @@ function AboutSettings() {
|
||||
{(updateStatus === 'downloading' || updateStatus === 'restarting') && (
|
||||
<div className="mt-3">
|
||||
<div className="h-1.5 bg-[var(--color-surface-container-low)] rounded-full overflow-hidden">
|
||||
<div
|
||||
className="h-full bg-[var(--color-text-accent)] transition-all duration-300"
|
||||
style={{ width: `${Math.min(progressPercent, 100)}%` }}
|
||||
/>
|
||||
{hasKnownProgress || updateStatus === 'restarting' ? (
|
||||
<div
|
||||
className="h-full bg-[var(--color-text-accent)] transition-all duration-300"
|
||||
style={{ width: `${Math.min(progressPercent, 100)}%` }}
|
||||
/>
|
||||
) : (
|
||||
<div className="h-full w-1/3 rounded-full bg-[var(--color-text-accent)]/75 animate-pulse" />
|
||||
)}
|
||||
</div>
|
||||
{!hasKnownProgress && updateStatus === 'downloading' && downloadedBytes > 0 && (
|
||||
<p className="mt-1 text-xs text-[var(--color-text-tertiary)]">
|
||||
{downloadedText}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{releaseNotes && availableVersion && (
|
||||
<div className="mt-3 rounded-lg bg-[var(--color-surface-container-low)] px-3 py-2">
|
||||
<div className="mt-3 rounded-lg bg-[var(--color-surface-container-low)] px-3 py-3">
|
||||
<div className="text-[11px] uppercase tracking-[0.14em] text-[var(--color-text-tertiary)]">
|
||||
{t('update.releaseNotes')}
|
||||
</div>
|
||||
<p className="mt-1 text-xs leading-5 text-[var(--color-text-secondary)] whitespace-pre-wrap">
|
||||
{releaseNotes}
|
||||
</p>
|
||||
<MarkdownRenderer
|
||||
content={releaseNotes}
|
||||
variant="document"
|
||||
className="mt-2 text-[13px] leading-6 text-[var(--color-text-secondary)] [&_h1]:text-lg [&_h2]:text-base [&_h3]:text-sm [&_p]:text-[13px] [&_p]:leading-6"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
||||
@ -15,6 +15,7 @@ describe('updateStore', () => {
|
||||
beforeEach(() => {
|
||||
check.mockReset()
|
||||
relaunch.mockReset()
|
||||
window.localStorage.clear()
|
||||
Object.defineProperty(window, '__TAURI_INTERNALS__', {
|
||||
configurable: true,
|
||||
value: {},
|
||||
@ -41,6 +42,53 @@ describe('updateStore', () => {
|
||||
expect(useUpdateStore.getState().shouldPrompt).toBe(true)
|
||||
})
|
||||
|
||||
it('does not re-prompt for the same version after dismissing once', async () => {
|
||||
check.mockResolvedValue({
|
||||
version: '0.2.0',
|
||||
body: 'Bug fixes and performance improvements',
|
||||
close: vi.fn().mockResolvedValue(undefined),
|
||||
})
|
||||
|
||||
vi.resetModules()
|
||||
const { useUpdateStore } = await import('./updateStore')
|
||||
|
||||
await useUpdateStore.getState().checkForUpdates()
|
||||
useUpdateStore.getState().dismissPrompt()
|
||||
|
||||
expect(useUpdateStore.getState().shouldPrompt).toBe(false)
|
||||
expect(window.localStorage.getItem('cc-haha-dismissed-update-version')).toBe('0.2.0')
|
||||
|
||||
await useUpdateStore.getState().checkForUpdates({ silent: true })
|
||||
|
||||
expect(useUpdateStore.getState().status).toBe('available')
|
||||
expect(useUpdateStore.getState().availableVersion).toBe('0.2.0')
|
||||
expect(useUpdateStore.getState().shouldPrompt).toBe(false)
|
||||
})
|
||||
|
||||
it('prompts again when a newer version is available after dismissing an older one', async () => {
|
||||
check
|
||||
.mockResolvedValueOnce({
|
||||
version: '0.2.0',
|
||||
body: 'Bug fixes and performance improvements',
|
||||
close: vi.fn().mockResolvedValue(undefined),
|
||||
})
|
||||
.mockResolvedValueOnce({
|
||||
version: '0.3.0',
|
||||
body: 'New release',
|
||||
close: vi.fn().mockResolvedValue(undefined),
|
||||
})
|
||||
|
||||
vi.resetModules()
|
||||
const { useUpdateStore } = await import('./updateStore')
|
||||
|
||||
await useUpdateStore.getState().checkForUpdates()
|
||||
useUpdateStore.getState().dismissPrompt()
|
||||
await useUpdateStore.getState().checkForUpdates({ silent: true })
|
||||
|
||||
expect(useUpdateStore.getState().availableVersion).toBe('0.3.0')
|
||||
expect(useUpdateStore.getState().shouldPrompt).toBe(true)
|
||||
})
|
||||
|
||||
it('computes download progress from content length and relaunches after install', async () => {
|
||||
const downloadAndInstall = vi.fn(async (onEvent?: (event: unknown) => void) => {
|
||||
onEvent?.({ event: 'Started', data: { contentLength: 200 } })
|
||||
|
||||
@ -15,6 +15,8 @@ type CheckOptions = {
|
||||
silent?: boolean
|
||||
}
|
||||
|
||||
const DISMISSED_UPDATE_VERSION_KEY = 'cc-haha-dismissed-update-version'
|
||||
|
||||
type UpdateStore = {
|
||||
status: UpdateStatus
|
||||
availableVersion: string | null
|
||||
@ -34,6 +36,30 @@ type UpdateStore = {
|
||||
let pendingUpdate: Update | null = null
|
||||
let startupCheckPromise: Promise<void> | null = null
|
||||
|
||||
function readDismissedUpdateVersion(): string | null {
|
||||
if (typeof window === 'undefined') return null
|
||||
|
||||
try {
|
||||
return window.localStorage.getItem(DISMISSED_UPDATE_VERSION_KEY)
|
||||
} catch {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
function writeDismissedUpdateVersion(version: string | null) {
|
||||
if (typeof window === 'undefined') return
|
||||
|
||||
try {
|
||||
if (version) {
|
||||
window.localStorage.setItem(DISMISSED_UPDATE_VERSION_KEY, version)
|
||||
} else {
|
||||
window.localStorage.removeItem(DISMISSED_UPDATE_VERSION_KEY)
|
||||
}
|
||||
} catch {
|
||||
// Ignore storage write failures.
|
||||
}
|
||||
}
|
||||
|
||||
async function setPendingUpdate(next: Update | null) {
|
||||
const previous = pendingUpdate
|
||||
pendingUpdate = next
|
||||
@ -93,6 +119,7 @@ export const useUpdateStore = create<UpdateStore>((set, get) => ({
|
||||
const checkedAt = Date.now()
|
||||
|
||||
if (!update) {
|
||||
writeDismissedUpdateVersion(null)
|
||||
set((state) => ({
|
||||
...state,
|
||||
status: 'up-to-date',
|
||||
@ -108,6 +135,9 @@ export const useUpdateStore = create<UpdateStore>((set, get) => ({
|
||||
return null
|
||||
}
|
||||
|
||||
const dismissedVersion = readDismissedUpdateVersion()
|
||||
const shouldPrompt = dismissedVersion !== update.version
|
||||
|
||||
set((state) => ({
|
||||
...state,
|
||||
status: 'available',
|
||||
@ -118,7 +148,7 @@ export const useUpdateStore = create<UpdateStore>((set, get) => ({
|
||||
totalBytes: null,
|
||||
checkedAt,
|
||||
error: null,
|
||||
shouldPrompt: true,
|
||||
shouldPrompt,
|
||||
}))
|
||||
return update
|
||||
} catch (error) {
|
||||
@ -160,6 +190,7 @@ export const useUpdateStore = create<UpdateStore>((set, get) => ({
|
||||
}))
|
||||
|
||||
try {
|
||||
writeDismissedUpdateVersion(null)
|
||||
const { relaunch } = await import('@tauri-apps/plugin-process')
|
||||
let totalBytes: number | null = null
|
||||
let downloadedBytes = 0
|
||||
@ -213,6 +244,7 @@ export const useUpdateStore = create<UpdateStore>((set, get) => ({
|
||||
},
|
||||
|
||||
dismissPrompt: () => {
|
||||
writeDismissedUpdateVersion(get().availableVersion)
|
||||
set((state) => ({
|
||||
...state,
|
||||
shouldPrompt: false,
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user