From 723885202d4280141dcb68a5ba025fea9254fd40 Mon Sep 17 00:00:00 2001 From: Adrian Bonpin Date: Mon, 13 Jul 2026 06:33:01 +0800 Subject: [PATCH 01/12] fix(plugin): space-between layout, no-shrink dropdown, horizontal padding --- .../src/components/LibraryAppPanel.tsx | 20 ++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/plugins/decky-vault/src/components/LibraryAppPanel.tsx b/plugins/decky-vault/src/components/LibraryAppPanel.tsx index 601a3a4..80a8df5 100644 --- a/plugins/decky-vault/src/components/LibraryAppPanel.tsx +++ b/plugins/decky-vault/src/components/LibraryAppPanel.tsx @@ -110,12 +110,12 @@ export default function LibraryAppPanel({ appId, title, hardwareSlug, baseUrl }: return ( <> - {/* Stats + device dropdown inline */} + {/* Stats + device dropdown — space-between, with padding */} -
- +
+ {data.estFps ? ( - + {data.estFps.avg} avg {data.estFps.onePct != null && {data.estFps.onePct} 1% low} {data.estFps.low != null && {data.estFps.low} min} @@ -127,11 +127,13 @@ export default function LibraryAppPanel({ appId, title, hardwareSlug, baseUrl }: No data yet )} - setDevice(opt.data as string)} - /> + + setDevice(opt.data as string)} + /> +
From ef967666596e092cc51d25445a339a58a73288ad Mon Sep 17 00:00:00 2001 From: Adrian Bonpin Date: Mon, 13 Jul 2026 06:37:27 +0800 Subject: [PATCH 02/12] fix(plugin): auto-detect device via DMI, remove selector, fall back to global --- .../src/components/LibraryAppPanel.tsx | 79 ++++++++----------- 1 file changed, 35 insertions(+), 44 deletions(-) diff --git a/plugins/decky-vault/src/components/LibraryAppPanel.tsx b/plugins/decky-vault/src/components/LibraryAppPanel.tsx index 80a8df5..4075a1b 100644 --- a/plugins/decky-vault/src/components/LibraryAppPanel.tsx +++ b/plugins/decky-vault/src/components/LibraryAppPanel.tsx @@ -1,18 +1,17 @@ import { useEffect, useState, useRef } from "react" -import { PanelSectionRow, DropdownItem, ButtonItem, staticClasses } from "@decky/ui" +import { PanelSectionRow, ButtonItem, staticClasses } from "@decky/ui" import { Router } from "@decky/ui" import { fetchPluginGame, - fetchPluginDevices, setPluginApiBaseUrl, type PluginGameResponse, - type PluginDeviceRow, } from "../lib/plugin-api" +import { getHardwareInfo } from "../lib/api" interface Props { appId: number title: string - hardwareSlug: string | null // detected device + hardwareSlug: string | null // detected device (from settings, may be null) baseUrl: string } @@ -34,10 +33,9 @@ function openExternalUrl(url: string) { export default function LibraryAppPanel({ appId, title, hardwareSlug, baseUrl }: Props) { const [data, setData] = useState(null) - const [devices, setDevices] = useState([]) const [loading, setLoading] = useState(true) - const [device, setDevice] = useState(hardwareSlug ?? "") // "" = all devices const [fetchError, setFetchError] = useState("") + const [detectedSlug, setDetectedSlug] = useState(hardwareSlug ?? null) const reqIdRef = useRef(0) useEffect(() => { @@ -47,13 +45,23 @@ export default function LibraryAppPanel({ appId, title, hardwareSlug, baseUrl }: async function load() { setLoading(true) try { - const d = await fetchPluginGame(appId, device || null, 3) + // Detect hardware if not set in settings + let slug = hardwareSlug ?? null + if (!slug) { + try { + const hw = await getHardwareInfo() + if (hw.slug && hw.slug !== "unknown") slug = hw.slug + } catch { + // Fall back to global + } + if (!cancelled) setDetectedSlug(slug) + } + + const d = await fetchPluginGame(appId, slug, 3) if (cancelled || id !== reqIdRef.current) return setFetchError(d.error && !d.game ? d.error : "") setData(d) setLoading(false) - const devs = await fetchPluginDevices(appId) - if (!cancelled && id === reqIdRef.current) setDevices(devs) } catch { if (!cancelled && id === reqIdRef.current) { setData(null) @@ -64,15 +72,7 @@ export default function LibraryAppPanel({ appId, title, hardwareSlug, baseUrl }: } load() return () => { cancelled = true } - }, [appId, device, baseUrl]) - - const deviceOptions = [ - { label: "All devices", data: "" }, - ...(hardwareSlug ? [{ label: `Your device (${hardwareSlug})`, data: hardwareSlug }] : []), - ...devices - .filter((d) => d.slug !== hardwareSlug) - .map((d) => ({ label: `${d.name} (${d.count})`, data: d.slug })), - ] + }, [appId, baseUrl]) // re-fetch only when app changes const gameUrl = data?.game?.steamAppId ? `${baseUrl}/game/${data.game.steamAppId}` @@ -81,7 +81,7 @@ export default function LibraryAppPanel({ appId, title, hardwareSlug, baseUrl }: if (loading) { return ( -
+
DeckyVault loading…
@@ -91,7 +91,7 @@ export default function LibraryAppPanel({ appId, title, hardwareSlug, baseUrl }: if (fetchError) { return ( -
+
DeckyVault: {fetchError}
@@ -101,7 +101,7 @@ export default function LibraryAppPanel({ appId, title, hardwareSlug, baseUrl }: if (!data || !data.game) { return ( -
+
Not on DeckyVault — add it
@@ -110,30 +110,21 @@ export default function LibraryAppPanel({ appId, title, hardwareSlug, baseUrl }: return ( <> - {/* Stats + device dropdown — space-between, with padding */} + {/* Stats row */} -
- - {data.estFps ? ( - - {data.estFps.avg} avg - {data.estFps.onePct != null && {data.estFps.onePct} 1% low} - {data.estFps.low != null && {data.estFps.low} min} - {data.estFps.high != null && {data.estFps.high} max} - {data.estFps.tdpAvg != null && {data.estFps.tdpAvg}W TDP} - ({data.estFps.count}) - - ) : ( - No data yet - )} - - - setDevice(opt.data as string)} - /> - +
+ {data.estFps ? ( + + {data.estFps.avg} avg + {data.estFps.onePct != null && {data.estFps.onePct} 1% low} + {data.estFps.low != null && {data.estFps.low} min} + {data.estFps.high != null && {data.estFps.high} max} + {data.estFps.tdpAvg != null && {data.estFps.tdpAvg}W TDP} + ({data.estFps.count}) + + ) : ( + No data yet + )}
From eedcf9efa613c2172d895d804a48532811204db9 Mon Sep 17 00:00:00 2001 From: Adrian Bonpin Date: Mon, 13 Jul 2026 06:39:09 +0800 Subject: [PATCH 03/12] fix(plugin): show global fallback warning, compact View Details button --- .../decky-vault/src/components/LibraryAppPanel.tsx | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/plugins/decky-vault/src/components/LibraryAppPanel.tsx b/plugins/decky-vault/src/components/LibraryAppPanel.tsx index 4075a1b..66e0685 100644 --- a/plugins/decky-vault/src/components/LibraryAppPanel.tsx +++ b/plugins/decky-vault/src/components/LibraryAppPanel.tsx @@ -125,14 +125,20 @@ export default function LibraryAppPanel({ appId, title, hardwareSlug, baseUrl }: ) : ( No data yet )} + {/* Device scope badge */} +
+ {detectedSlug ? detectedSlug : ⚠ global (device not detected)} +
- {/* View Details button */} + {/* Compact View Details button */} - openExternalUrl(gameUrl)}> - View Details - +
+ openExternalUrl(gameUrl)} style={{ width: "auto", minWidth: "120px" }}> + View Details + +
) From a66c13daa029d8c56afdfb0169dab902d3b5f6da Mon Sep 17 00:00:00 2001 From: Adrian Bonpin Date: Mon, 13 Jul 2026 06:41:15 +0800 Subject: [PATCH 04/12] =?UTF-8?q?fix(plugin):=20single=20row=20=E2=80=94?= =?UTF-8?q?=20stats=20+=20View=20Details=20inline,=20space-between?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/components/LibraryAppPanel.tsx | 49 +++++++++++-------- 1 file changed, 28 insertions(+), 21 deletions(-) diff --git a/plugins/decky-vault/src/components/LibraryAppPanel.tsx b/plugins/decky-vault/src/components/LibraryAppPanel.tsx index 66e0685..a06d889 100644 --- a/plugins/decky-vault/src/components/LibraryAppPanel.tsx +++ b/plugins/decky-vault/src/components/LibraryAppPanel.tsx @@ -1,5 +1,5 @@ import { useEffect, useState, useRef } from "react" -import { PanelSectionRow, ButtonItem, staticClasses } from "@decky/ui" +import { PanelSectionRow, staticClasses } from "@decky/ui" import { Router } from "@decky/ui" import { fetchPluginGame, @@ -109,12 +109,12 @@ export default function LibraryAppPanel({ appId, title, hardwareSlug, baseUrl }: } return ( - <> - {/* Stats row */} - -
+ +
+ {/* Stats */} + {data.estFps ? ( - + {data.estFps.avg} avg {data.estFps.onePct != null && {data.estFps.onePct} 1% low} {data.estFps.low != null && {data.estFps.low} min} @@ -126,20 +126,27 @@ export default function LibraryAppPanel({ appId, title, hardwareSlug, baseUrl }: No data yet )} {/* Device scope badge */} -
- {detectedSlug ? detectedSlug : ⚠ global (device not detected)} -
-
-
- - {/* Compact View Details button */} - -
- openExternalUrl(gameUrl)} style={{ width: "auto", minWidth: "120px" }}> - View Details - -
-
- + + {detectedSlug ? detectedSlug : ⚠ global} + + + {/* View Details — compact inline button */} + +
+
) } \ No newline at end of file From 17d70f175d358e316d57b69d4fd4f7de8c1e59f1 Mon Sep 17 00:00:00 2001 From: Adrian Bonpin Date: Mon, 13 Jul 2026 06:44:36 +0800 Subject: [PATCH 05/12] fix(plugin): use ButtonItem for gamepad-navigable View Details --- .../src/components/LibraryAppPanel.tsx | 24 ++++++------------- 1 file changed, 7 insertions(+), 17 deletions(-) diff --git a/plugins/decky-vault/src/components/LibraryAppPanel.tsx b/plugins/decky-vault/src/components/LibraryAppPanel.tsx index a06d889..1cc7e5b 100644 --- a/plugins/decky-vault/src/components/LibraryAppPanel.tsx +++ b/plugins/decky-vault/src/components/LibraryAppPanel.tsx @@ -1,5 +1,5 @@ import { useEffect, useState, useRef } from "react" -import { PanelSectionRow, staticClasses } from "@decky/ui" +import { PanelSectionRow, ButtonItem, staticClasses } from "@decky/ui" import { Router } from "@decky/ui" import { fetchPluginGame, @@ -130,22 +130,12 @@ export default function LibraryAppPanel({ appId, title, hardwareSlug, baseUrl }: {detectedSlug ? detectedSlug : ⚠ global} - {/* View Details — compact inline button */} - + {/* View Details — compact, gamepad-navigable */} + + openExternalUrl(gameUrl)}> + View Details + +
) From 4f503e373a652fed4267345e99d7dbfc4b468df5 Mon Sep 17 00:00:00 2001 From: Adrian Bonpin Date: Mon, 13 Jul 2026 06:47:51 +0800 Subject: [PATCH 06/12] fix(plugin): use Focusable for compact styled View Details button --- .../src/components/LibraryAppPanel.tsx | 20 +++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/plugins/decky-vault/src/components/LibraryAppPanel.tsx b/plugins/decky-vault/src/components/LibraryAppPanel.tsx index 1cc7e5b..ec6cb79 100644 --- a/plugins/decky-vault/src/components/LibraryAppPanel.tsx +++ b/plugins/decky-vault/src/components/LibraryAppPanel.tsx @@ -1,5 +1,5 @@ import { useEffect, useState, useRef } from "react" -import { PanelSectionRow, ButtonItem, staticClasses } from "@decky/ui" +import { PanelSectionRow, Focusable, staticClasses } from "@decky/ui" import { Router } from "@decky/ui" import { fetchPluginGame, @@ -130,12 +130,20 @@ export default function LibraryAppPanel({ appId, title, hardwareSlug, baseUrl }: {detectedSlug ? detectedSlug : ⚠ global} - {/* View Details — compact, gamepad-navigable */} - - openExternalUrl(gameUrl)}> + {/* View Details — compact, gamepad-navigable via Focusable */} + openExternalUrl(gameUrl)} style={{ flexShrink: 0 }}> + View Details - - + +
) From 210046c97eec8216ac46f47b5e31ad1908a85807 Mon Sep 17 00:00:00 2001 From: Adrian Bonpin Date: Mon, 13 Jul 2026 06:52:36 +0800 Subject: [PATCH 07/12] style: extend navbar breakpoints to lg to move to collapsable sidebar (for smaller devices) --- apps/web/components/navbar.tsx | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/apps/web/components/navbar.tsx b/apps/web/components/navbar.tsx index eb0fb75..78d9cd9 100644 --- a/apps/web/components/navbar.tsx +++ b/apps/web/components/navbar.tsx @@ -151,7 +151,7 @@ export default function Navbar() { initial={{ opacity: 0, y: "-100%" }} animate={{ opacity: 1, y: 0 }} exit={{ opacity: 0, y: "-100%" }} - className='w-full flex flex-row px-4 md:px-[10svw] py-2 items-center gap-4 md:gap-8 justify-between border-b border-border sticky top-0 z-50 backdrop-blur-sm bg-background/80' + className='w-full flex flex-row px-4 lg:px-[10svw] py-2 items-center gap-4 lg:gap-8 justify-between border-b border-border sticky top-0 z-50 backdrop-blur-sm bg-background/80' > {!isLanding && ( - + DeckyVault )} @@ -233,7 +233,7 @@ export default function Navbar() { {/* Desktop Navigation Links */} -
    +
      {navbarRoutes.map((route) => ( setMobileMenuOpen(true)} - className='flex md:hidden flex-row items-center justify-center p-2 -mr-2 rounded-md hover:bg-text/5 transition-colors cursor-pointer' + className='flex lg:hidden flex-row items-center justify-center p-2 -mr-2 rounded-md hover:bg-text/5 transition-colors cursor-pointer' aria-label='Open menu' > @@ -369,7 +369,7 @@ export default function Navbar() { animate={{ opacity: 1 }} exit={{ opacity: 0 }} transition={{ duration: 0.2 }} - className='fixed inset-0 bg-black/50 z-60 md:hidden' + className='fixed inset-0 bg-black/50 z-60 lg:hidden' onClick={() => setMobileMenuOpen(false)} />
      Menu From c02ae28982199300df946ea286b09cb2d95c0223 Mon Sep 17 00:00:00 2001 From: Adrian Bonpin Date: Mon, 13 Jul 2026 06:54:25 +0800 Subject: [PATCH 08/12] fix(plugin): use DialogButtonPrimary for View Details (like HLTB) --- .../src/components/LibraryAppPanel.tsx | 20 +++++-------------- 1 file changed, 5 insertions(+), 15 deletions(-) diff --git a/plugins/decky-vault/src/components/LibraryAppPanel.tsx b/plugins/decky-vault/src/components/LibraryAppPanel.tsx index ec6cb79..991661f 100644 --- a/plugins/decky-vault/src/components/LibraryAppPanel.tsx +++ b/plugins/decky-vault/src/components/LibraryAppPanel.tsx @@ -1,5 +1,5 @@ import { useEffect, useState, useRef } from "react" -import { PanelSectionRow, Focusable, staticClasses } from "@decky/ui" +import { PanelSectionRow, DialogButtonPrimary, staticClasses } from "@decky/ui" import { Router } from "@decky/ui" import { fetchPluginGame, @@ -130,20 +130,10 @@ export default function LibraryAppPanel({ appId, title, hardwareSlug, baseUrl }: {detectedSlug ? detectedSlug : ⚠ global} - {/* View Details — compact, gamepad-navigable via Focusable */} - openExternalUrl(gameUrl)} style={{ flexShrink: 0 }}> - - View Details - - + {/* View Details — compact gamepad button */} + openExternalUrl(gameUrl)} style={{ flexShrink: 0, whiteSpace: "nowrap" }}> + View Details +
      ) From 2a1943cf3506b550c4bb6f03ab21424c4a6d7546 Mon Sep 17 00:00:00 2001 From: Adrian Bonpin Date: Mon, 13 Jul 2026 06:55:44 +0800 Subject: [PATCH 09/12] fix(plugin): constrain View Details button width to 130px --- plugins/decky-vault/src/components/LibraryAppPanel.tsx | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/plugins/decky-vault/src/components/LibraryAppPanel.tsx b/plugins/decky-vault/src/components/LibraryAppPanel.tsx index 991661f..98fe334 100644 --- a/plugins/decky-vault/src/components/LibraryAppPanel.tsx +++ b/plugins/decky-vault/src/components/LibraryAppPanel.tsx @@ -130,10 +130,12 @@ export default function LibraryAppPanel({ appId, title, hardwareSlug, baseUrl }: {detectedSlug ? detectedSlug : ⚠ global} - {/* View Details — compact gamepad button */} - openExternalUrl(gameUrl)} style={{ flexShrink: 0, whiteSpace: "nowrap" }}> - View Details - + {/* View Details — constrained width */} +
      + openExternalUrl(gameUrl)}> + View Details + +
) From 9b38b17eb0c64e46205fcbcc7ee9cefceab5eae2 Mon Sep 17 00:00:00 2001 From: Adrian Bonpin Date: Mon, 13 Jul 2026 07:03:28 +0800 Subject: [PATCH 10/12] =?UTF-8?q?fix(plugin):=20prevent=20overflow=20?= =?UTF-8?q?=E2=80=94=20shrink=20stats,=20constrain=20button=20to=20110px,?= =?UTF-8?q?=20hidden=20overflow?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- plugins/decky-vault/src/components/LibraryAppPanel.tsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/plugins/decky-vault/src/components/LibraryAppPanel.tsx b/plugins/decky-vault/src/components/LibraryAppPanel.tsx index 98fe334..4f34272 100644 --- a/plugins/decky-vault/src/components/LibraryAppPanel.tsx +++ b/plugins/decky-vault/src/components/LibraryAppPanel.tsx @@ -110,9 +110,9 @@ export default function LibraryAppPanel({ appId, title, hardwareSlug, baseUrl }: return ( -
+
{/* Stats */} - + {data.estFps ? ( {data.estFps.avg} avg @@ -130,8 +130,8 @@ export default function LibraryAppPanel({ appId, title, hardwareSlug, baseUrl }: {detectedSlug ? detectedSlug : ⚠ global} - {/* View Details — constrained width */} -
+ {/* View Details — compact, gamepad-navigable, constrained */} +
openExternalUrl(gameUrl)}> View Details From 3b270949738f170d88d5a4b469a95c07e40cbcb1 Mon Sep 17 00:00:00 2001 From: Adrian Bonpin Date: Mon, 13 Jul 2026 07:05:27 +0800 Subject: [PATCH 11/12] fix(plugin): inject CSS !important to constrain DialogButtonPrimary width, drop PanelSectionRow wrapper --- .../src/components/LibraryAppPanel.tsx | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/plugins/decky-vault/src/components/LibraryAppPanel.tsx b/plugins/decky-vault/src/components/LibraryAppPanel.tsx index 4f34272..864ebe2 100644 --- a/plugins/decky-vault/src/components/LibraryAppPanel.tsx +++ b/plugins/decky-vault/src/components/LibraryAppPanel.tsx @@ -109,8 +109,11 @@ export default function LibraryAppPanel({ appId, title, hardwareSlug, baseUrl }: } return ( - -
+ <> + +
{/* Stats */} {data.estFps ? ( @@ -130,13 +133,11 @@ export default function LibraryAppPanel({ appId, title, hardwareSlug, baseUrl }: {detectedSlug ? detectedSlug : ⚠ global} - {/* View Details — compact, gamepad-navigable, constrained */} -
- openExternalUrl(gameUrl)}> - View Details - -
+ {/* View Details — gamepad-navigable, CSS-constrained width */} + openExternalUrl(gameUrl)}> + View Details +
- + ) } \ No newline at end of file From a8e189ca843062bc8235d9a03ad987c4f8d87bd1 Mon Sep 17 00:00:00 2001 From: Adrian Bonpin Date: Mon, 13 Jul 2026 07:07:42 +0800 Subject: [PATCH 12/12] fix(plugin): add 16px horizontal padding to stats row --- plugins/decky-vault/src/components/LibraryAppPanel.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/decky-vault/src/components/LibraryAppPanel.tsx b/plugins/decky-vault/src/components/LibraryAppPanel.tsx index 864ebe2..8899671 100644 --- a/plugins/decky-vault/src/components/LibraryAppPanel.tsx +++ b/plugins/decky-vault/src/components/LibraryAppPanel.tsx @@ -113,7 +113,7 @@ export default function LibraryAppPanel({ appId, title, hardwareSlug, baseUrl }: -
+
{/* Stats */} {data.estFps ? (