diff --git a/src/App.tsx b/src/App.tsx index 25844fa..29e7fed 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -698,7 +698,7 @@ function App() { }, []); useEffect(() => { - if (!coreReady || !autoAddClipboardLinks) return; + if (!coreReady || showKeychainModal || !autoAddClipboardLinks) return; let active = true; let wasForeground = false; @@ -725,6 +725,7 @@ function App() { // read was pending. Let that newer Add-modal request win unchanged. if ( !currentSettings.autoAddClipboardLinks || + currentSettings.showKeychainModal || currentStore.pendingAddRequestVersion !== requestVersionBeforeRead ) { return; @@ -772,7 +773,7 @@ function App() { window.removeEventListener('blur', handleForegroundChange); document.removeEventListener('visibilitychange', handleForegroundChange); }; - }, [autoAddClipboardLinks, coreReady]); + }, [autoAddClipboardLinks, coreReady, showKeychainModal]); useEffect(() => { const root = window.document.documentElement; diff --git a/src/components/DownloadItem.tsx b/src/components/DownloadItem.tsx index 98d53a9..b7a78aa 100644 --- a/src/components/DownloadItem.tsx +++ b/src/components/DownloadItem.tsx @@ -99,10 +99,10 @@ export const DownloadItem = React.memo(({ {hasDownloadedAmount ? ( @@ -110,7 +110,7 @@ export const DownloadItem = React.memo(({ {sizeDisplay.downloaded} / - {sizeDisplay.totalIsEstimate ? '~' : ''}{sizeDisplay.total} + {sizeDisplay.totalIsEstimate ? '~' : ''}{sizeDisplay.total} {sizeDisplay.unit} ) : sizeDisplay.fallback} diff --git a/src/components/PropertiesModal.tsx b/src/components/PropertiesModal.tsx index b9ec3bc..6682c76 100644 --- a/src/components/PropertiesModal.tsx +++ b/src/components/PropertiesModal.tsx @@ -237,7 +237,7 @@ export const PropertiesModal = () => { {hasDownloadedAmount ? ( @@ -245,7 +245,7 @@ export const PropertiesModal = () => { {sizeDisplay.downloaded} / - {sizeDisplay.totalIsEstimate ? '~' : ''}{sizeDisplay.total} + {sizeDisplay.totalIsEstimate ? '~' : ''}{sizeDisplay.total} {sizeDisplay.unit} ) : sizeDisplay.fallback} diff --git a/src/utils/downloadProgress.test.ts b/src/utils/downloadProgress.test.ts index 75aa308..a6f8a44 100644 --- a/src/utils/downloadProgress.test.ts +++ b/src/utils/downloadProgress.test.ts @@ -14,10 +14,23 @@ describe('download progress size display', () => { totalIsEstimate: true, fallbackSize: 'Unknown' })).toEqual({ - downloaded: '1.20 GB', - total: '2.40 GB', + downloaded: '1.20', + total: '2.40', + unit: 'GB', totalIsEstimate: true, fallback: 'Unknown' }); }); + + it('converts downloaded bytes into the total size unit', () => { + expect(resolveDownloadSizeDisplay({ + downloadedBytes: 512 * 1024 ** 2, + totalBytes: 2 * 1024 ** 3, + fallbackSize: '2 GB' + })).toMatchObject({ + downloaded: '0.50', + total: '2.00', + unit: 'GB' + }); + }); }); diff --git a/src/utils/downloadProgress.ts b/src/utils/downloadProgress.ts index e2a473c..31baa4b 100644 --- a/src/utils/downloadProgress.ts +++ b/src/utils/downloadProgress.ts @@ -1,26 +1,37 @@ export interface DownloadSizeDisplay { downloaded: string | null; total: string | null; + unit: string | null; totalIsEstimate: boolean; fallback: string; } +const BYTE_UNITS = ['B', 'KB', 'MB', 'GB', 'TB'] as const; + const isUsableByteCount = (value: number | null | undefined): value is number => typeof value === 'number' && Number.isFinite(value) && value >= 0; -export const formatDownloadBytes = (bytes: number): string => { - if (bytes < 1024) return `${Math.round(bytes)} B`; - - const units = ['KB', 'MB', 'GB', 'TB']; +const byteUnitIndex = (bytes: number): number => { let value = bytes; - let unitIndex = -1; - while (value >= 1024 && unitIndex < units.length - 1) { + let unitIndex = 0; + while (value >= 1024 && unitIndex < BYTE_UNITS.length - 1) { value /= 1024; unitIndex += 1; } + return unitIndex; +}; +const formatDownloadBytesInUnit = (bytes: number, unitIndex: number): string => { + const value = bytes / 1024 ** unitIndex; const precision = value >= 100 ? 0 : value >= 10 ? 1 : 2; - return `${value.toFixed(precision)} ${units[unitIndex]}`; + return value < 1024 && unitIndex === 0 + ? `${Math.round(value)}` + : value.toFixed(precision); +}; + +export const formatDownloadBytes = (bytes: number): string => { + const unitIndex = byteUnitIndex(bytes); + return `${formatDownloadBytesInUnit(bytes, unitIndex)} ${BYTE_UNITS[unitIndex]}`; }; export const resolveDownloadSizeDisplay = ({ @@ -34,8 +45,15 @@ export const resolveDownloadSizeDisplay = ({ totalIsEstimate?: boolean; fallbackSize?: string | null; }): DownloadSizeDisplay => ({ - downloaded: isUsableByteCount(downloadedBytes) ? formatDownloadBytes(downloadedBytes) : null, - total: isUsableByteCount(totalBytes) && totalBytes > 0 ? formatDownloadBytes(totalBytes) : null, + downloaded: isUsableByteCount(downloadedBytes) && isUsableByteCount(totalBytes) && totalBytes > 0 + ? formatDownloadBytesInUnit(downloadedBytes, byteUnitIndex(totalBytes)) + : null, + total: isUsableByteCount(totalBytes) && totalBytes > 0 + ? formatDownloadBytesInUnit(totalBytes, byteUnitIndex(totalBytes)) + : null, + unit: isUsableByteCount(totalBytes) && totalBytes > 0 + ? BYTE_UNITS[byteUnitIndex(totalBytes)] + : null, totalIsEstimate: Boolean(totalIsEstimate && isUsableByteCount(totalBytes) && totalBytes > 0), fallback: fallbackSize && fallbackSize !== '-' ? fallbackSize : 'Unknown' }); diff --git a/src/utils/keychainStartup.test.ts b/src/utils/keychainStartup.test.ts index a790fd6..64fda54 100644 --- a/src/utils/keychainStartup.test.ts +++ b/src/utils/keychainStartup.test.ts @@ -80,6 +80,19 @@ describe('getKeychainStartupDecision', () => { }); }); + it('keeps persistent access deferred without prompting when the version API is unavailable', () => { + expect(getKeychainStartupDecision({ + portable: false, + appVersion: '', + approvedVersion: '1.0.5', + accessGranted: true, + promptDismissed: false + })).toEqual({ + deferKeychainHydration: true, + showKeychainPrompt: false + }); + }); + it('never gates portable pairing on credential-store access', () => { expect(getKeychainStartupDecision({ portable: true, diff --git a/src/utils/keychainStartup.ts b/src/utils/keychainStartup.ts index a073c99..01ab716 100644 --- a/src/utils/keychainStartup.ts +++ b/src/utils/keychainStartup.ts @@ -25,9 +25,11 @@ export const getKeychainStartupDecision = ({ }; } - const versionChanged = Boolean(appVersion) && approvedVersion !== appVersion; + const versionKnown = Boolean(appVersion.trim()); + const versionChanged = versionKnown && approvedVersion !== appVersion; + const mustDeferAccess = !accessGranted || !versionKnown || versionChanged; return { - deferKeychainHydration: !accessGranted || versionChanged, + deferKeychainHydration: mustDeferAccess, showKeychainPrompt: versionChanged || (!accessGranted && !promptDismissed) }; };