From 877e2f1d6077eaba69ef73ec08099b1b9dc984dd Mon Sep 17 00:00:00 2001 From: slush97 Date: Wed, 19 Aug 2026 13:33:47 -0700 Subject: [PATCH 1/4] fix(locker): center general empty states --- src/pages/Locker.tsx | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/src/pages/Locker.tsx b/src/pages/Locker.tsx index 1660bafe..4d954b5f 100644 --- a/src/pages/Locker.tsx +++ b/src/pages/Locker.tsx @@ -1999,6 +1999,13 @@ function LockerGlobalView({ groups, hideNsfw, onBack, onToggle, onSetGlobalType, // card's render rect to this element so models never bleed past the pane. const paneRef = useRef(null); + // Empty tabs should read as a compact card in the pane, not as a wide banner. + // `my-auto` centers the card in the space below the tab heading, while the + // small upward translation compensates for that heading so the card remains + // visually centered in the pane as a whole. + const emptyTabClass = + 'my-auto flex min-h-60 w-full max-w-md -translate-y-5 self-center flex-col items-center justify-center gap-3 rounded-xl border border-white/15 bg-bg-secondary/70 px-8 py-10 text-center shadow-2xl shadow-black/25 backdrop-blur-md'; + // Where each visible card shuffles, asked once per card. A custom tab mixes // hero skins, classified mods and non-shuffleable ones, so the affordance is // decided per mod (shuffleGroupKind) rather than per tab. @@ -2254,7 +2261,7 @@ function LockerGlobalView({ groups, hideNsfw, onBack, onToggle, onSetGlobalType,
{activeType ? ( <> @@ -2317,7 +2324,7 @@ function LockerGlobalView({ groups, hideNsfw, onBack, onToggle, onSetGlobalType, )}
{activeMods.length === 0 && isPropContainer ? ( -
+
{activeType === 'spirit-urn' ? ( ) : ( @@ -2338,7 +2345,7 @@ function LockerGlobalView({ groups, hideNsfw, onBack, onToggle, onSetGlobalType,
) : activeMods.length === 0 && isPriorityTab ? ( -
+

{t('locker.globalPicker.empty')}

@@ -567,67 +578,70 @@ export default function PerformanceConfigCard() { )} {selected && selectedRelease && ( - - ), - kofi: ( - - ), - }} - /> - ) : null + + )} + + +
+
+ void onToggleTrackLatest(on)} + label={ + + {t('performance.trackLatest.label')} + } + disabled={busy} + /> + {latestLine &&

{latestLine}

} +
+ + {selectedRelease && ( + void onChangeOptIns(keys)} + disabled={busy} /> )} -
- -
- void onToggleTrackLatest(on)} - label={t('performance.trackLatest.label')} - description={t('performance.trackLatest.description')} - disabled={busy} - /> - {latestLine && ( - // Indent past the switch (w-11 + gap-3) so the line reads as part - // of the toggle's description column. -

{latestLine}

- )} -
- - {selectedRelease && ( - void onChangeOptIns(keys)} - disabled={busy} - /> - )} - -
+ + +
-
-

- {look &&

+
+ {look &&
+ {showStatusDetail && ( +

{status ? performanceStatusMessage( status, @@ -637,17 +651,17 @@ export default function PerformanceConfigCard() { latestUsable ? latest!.version! : undefined ) : t('performance.checkingGameinfo')} - -

+

+ )} {pinnedOlder && !pendingVersion && ( -

+

{t('performance.version.pinned', { version: selectedVersion, latest: selected?.versions[0].version ?? '', })}

)} - {openError &&

{openError}

} + {openError &&

{openError}

}
@@ -729,8 +743,7 @@ export default function PerformanceConfigCard() {
)} -

{t('performance.shadowsHint')}

-
+
{pickerOpen && ( onSelect(e.target.value)}> {presets.map((preset) => ( ))} diff --git a/src/components/performance/PresetSummary.tsx b/src/components/performance/PresetSummary.tsx index c107adfe..586d567e 100644 --- a/src/components/performance/PresetSummary.tsx +++ b/src/components/performance/PresetSummary.tsx @@ -47,7 +47,7 @@ export default function PresetSummary({ preset, release, creditSlot }: PresetSum : t('performance.preset.pinnedCommit', { ref: release.ref }); return ( -
+
{t(`performance.preset.tier.${preset.tier}`)} @@ -60,35 +60,34 @@ export default function PresetSummary({ preset, release, creditSlot }: PresetSum
-

+

{t(`performance.preset.tierBlurb.${preset.tier}`)}

{preset.unstable && ( -

+

)} -

- {t('performance.preset.credit', { - credit: preset.upstream.credit, - license: preset.upstream.license, - })}{' '} - {creditSlot}{' '} - - -

+
+ {preset.upstream.credit} · {preset.upstream.license} +
+ {creditSlot && {creditSlot}} + + +
+
); } diff --git a/src/components/performance/VersionHistoryModal.test.ts b/src/components/performance/VersionHistoryModal.test.ts new file mode 100644 index 00000000..e12c5b58 --- /dev/null +++ b/src/components/performance/VersionHistoryModal.test.ts @@ -0,0 +1,36 @@ +import { describe, expect, it } from 'vitest'; +import type { PerformanceRemoteVersion } from '../../types/electron'; +import { performanceHistoryRowCopy } from '../../lib/performanceHistory'; + +function entry(label: string | null, ref = '96ff42d1'): PerformanceRemoteVersion { + return { + ref, + version: ref, + commit: ref.padEnd(40, '0'), + date: '2026-08-17', + label, + }; +} + +describe('performanceHistoryRowCopy', () => { + it('promotes a prose release number and removes it from the detail', () => { + expect(performanceHistoryRowCopy(entry('2.9.1 release'))).toEqual({ + primary: '2.9.1', + detail: 'release', + }); + }); + + it('uses the known bundled version when its commit message has no version', () => { + expect(performanceHistoryRowCopy(entry('readme update'), '2.8.2')).toEqual({ + primary: '2.8.2', + detail: 'readme update', + }); + }); + + it('keeps the short commit when no release number is known', () => { + expect(performanceHistoryRowCopy(entry('minor documentation update', 'be2d3889'))).toEqual({ + primary: 'be2d3889', + detail: 'minor documentation update', + }); + }); +}); diff --git a/src/components/performance/VersionHistoryModal.tsx b/src/components/performance/VersionHistoryModal.tsx index 499dfa75..8d68e29f 100644 --- a/src/components/performance/VersionHistoryModal.tsx +++ b/src/components/performance/VersionHistoryModal.tsx @@ -4,6 +4,7 @@ import { CircleAlert, HardDriveDownload, Loader2, Package } from 'lucide-react'; import { Modal } from '../common/Modal'; import { ModalHeader } from '../common/ui'; import { fetchPerformanceRemoteVersion, listPerformanceRemoteVersions } from '../../lib/api'; +import { performanceHistoryRowCopy } from '../../lib/performanceHistory'; import type { PerformanceLatestInfo, PerformancePresetSummary, @@ -42,7 +43,10 @@ export default function VersionHistoryModal({ const [fetchingRef, setFetchingRef] = useState(null); const [fetchError, setFetchError] = useState(null); - const bundled = new Set(preset.versions.map((v) => v.version)); + const bundledVersionFor = (entry: PerformanceRemoteVersion) => + preset.versions.find( + (version) => version.version === entry.version || (!!entry.commit && version.commit === entry.commit) + )?.version; useEffect(() => { let cancelled = false; @@ -62,8 +66,9 @@ export default function VersionHistoryModal({ const pick = async (entry: PerformanceRemoteVersion) => { setFetchError(null); - if (bundled.has(entry.version)) { - onPickBundled(entry.version); + const bundledVersion = bundledVersionFor(entry); + if (bundledVersion) { + onPickBundled(bundledVersion); return; } setFetchingRef(entry.ref); @@ -107,8 +112,10 @@ export default function VersionHistoryModal({

{t('performance.history.empty')}

)} {versions?.map((entry) => { - const isBundled = bundled.has(entry.version); - const isSelected = entry.version === selectedVersion; + const bundledVersion = bundledVersionFor(entry); + const row = performanceHistoryRowCopy(entry, bundledVersion); + const isBundled = !!bundledVersion; + const isSelected = (bundledVersion ?? entry.version) === selectedVersion; const isFetching = fetchingRef === entry.ref; return (