Skip to content

Add Deck Shelves to Plugin Store#1018

Merged
beebls merged 19 commits into
SteamDeckHomebrew:mainfrom
santojon:submit-Deck-Shelves-v1.0.0
May 6, 2026
Merged

Add Deck Shelves to Plugin Store#1018
beebls merged 19 commits into
SteamDeckHomebrew:mainfrom
santojon:submit-Deck-Shelves-v1.0.0

Conversation

@santojon
Copy link
Copy Markdown
Contributor

@santojon santojon commented Apr 2, 2026

Add Deck Shelves to Plugin Store

Features

Deck Shelves — Project Overview

Condensed technical reference: what the plugin is, how it's built, what
works well, and where the friction is.


1. What it is

Deck Shelves is a plugin for Steam Deck (SteamOS GamepadUI) that injects custom shelves into the Home screen (library/home), below the native "Recently Played" row. Each shelf is a list of games resolved from a source (a Steam collection, a library tab, or a custom filter) with its own visual options.

The plugin also ships a full editor in the QAM (Quick Access Menu) for creating, editing, reordering, hiding, duplicating, importing and exporting shelves — without leaving the in-game UI.


2. Design principles

  • Stability above all. The plugin lives inside the Steam Deck home; any regression hits the user every day.
  • Smallest possible change. Surgical edits, no broad refactors without a strong reason.
  • Don't break the contract with the plugin host or with integrations (TabMaster, UnifiDeck, Non-Steam Badges).
  • Steam Deck performance is critical. Avoid re-renders, polling, idle timers, heavy work — the CPU is shared with the OS and the running game.
  • Cleanup is mandatory in every useEffect, listener and observer.
  • Respect the native UI — maximum reuse of Steam classes/components via webpack discovery; CSS-theme friendly.
  • Critical areas: src/runtime/homePatch.tsx, src/core/*, src/integrations/* — minimal, well-justified changes only.
  • Error flow stays in our scope. No dependence on host DOM/API for detection or reporting.

3. Features in detail

3.1 Custom shelves on Home

  • Inserted via DOM bridge + React portal; rendered in #deck-shelves-home-root created by runtime/homePatch.tsx as a sibling of the native recents row.
  • Each shelf is a DeckRow — header with collapse/expand title (saved in localStorage under ds-collapsed-{id}) plus a horizontally-scrollable row of GameCard items.
  • Three source types (in types.ts > ShelfSourceSchema):
    • collection (Favorites, user folder, [Unifideck] Installed, etc.).
    • tab (library tab, including tabs created by other plugins — runtime detection).
    • filter (FilterGroup with nested AND/OR).

3.2 Filter system (FilterGroup)

Schema in types.ts > FilterGroupSchema: a tree of FilterItems combined under mode: "and" | "or". Supported types:

installed, favorites, nonSteam, hidden, updatePending, isNew, deckCompatibility, playedWithinDays, playtimeRange, nameIncludes, nameRegex, friends, storeTag, achievements, collection, developer, publisher, appIdList, cloudAvailable, controllerSupport, merge.

cloudAvailable matches apps with bCloudAvailable === true; controllerSupport accepts min (default 1 = partial or full, 2 = full only) compared against nControllerSupport. Both invertible.

Sort: alphabetical, recent, playtime, release_date, size_on_disk, metacritic, review_score, added, random, manual.

Manual sort — when shelf.sort === "manual" (or shelf.source.filter.sort === "manual" for filter sources), applyManualOrder(ids, shelf.manualOrder) in src/steam/index.ts reorders the resolved ids by shelf.manualOrder: number[], with new ids (not in manualOrder) ordered by manualBaseSort.

3.3 Per-shelf and global toggles

  • matchNativeSize — card dimensions match the native shelf (measured at runtime via core/webpackCompat.ts > discoverNativeCardDimensions).
  • highlightFirst / highlightAll — render the first / all cards as featured landscape.
  • highlightedAppIds?: number[] — explicit list of appids to render as featured.
  • manualOrder / manualBaseSort — when sort === "manual".
  • hideStatusLine, hideNewBadge, hideCompatIcons, hideNonSteamBadge, hideShelfTitle, hideGameNames, hideInstallIndicator — fine-grained card element controls.

3.4 Home-specific global toggles

  • hideRecents — hides the native recents row; when active, the first shelf is force-expanded.
  • shelfHeroBackground — first shelf gets a hero background sourced from the focused card's art.
  • hideHomeTabs — hides the home tab strip, identified dynamically via [role="tablist"] siblings near the mount.
  • recentsReplaceSource — uses a Deck Shelves shelf as a drop-in replacement for the native recents row.

3.5 Quick Access Menu (QAM)

Full editor in components/DeckQAMSettings.tsx:

  • Master enabled toggle.
  • Sortable shelves list with actions + pointer-hold drag.
  • Shelves section — inline action row: Add, Import overflow (ImportMenuButton collapses one entry to a direct icon, two or more to a […] overflow), Export, Reset. Plugins register import types via registerImportType and show up in the same overflow menu.
  • Smart Shelves section — Add smart, smart-specific Import overflow, Export, Reset.
  • Saved Filters sectionCollapsibleSection hidden when settings.savedFilters is empty; inline rename + delete.
  • Apply globally — global toggles.
  • Footer — Import all / Export all / Reset all.
  • Modals — Export, Import, Reset, Template picker, Smart shelf picker, Delete confirmation.
  • Banners — Mount crash, Recents replace error.
  • About (book icon) — integrated docs (Overview, How to, Shelves, Filters, Sort, Smart Shelves, Support).

3.6 Integrations

  • TabMaster — import tabs as shelves (native and filter-based); detection via window.TabMasterStore + reading its settings.json.
  • UnifiDeck — managed-app detection; cross-references the [Unifideck] Installed collection.
  • Non-Steam Badges — presence detection enables hideNonSteamBadge.
  • SteamGridDB / HLTB / CheatDeck — coexistence validated.

3.7 Manual ordering + drag reorder

When sort === "manual", EditShelfModal renders ManualSortRow in the Source tab. Pointer-hold drag (300 ms timer + move-cancel + hit-test) lives in the useContainerDragReorder<T> hook in src/core/reorder.ts — shared by ManualSortRow, the QAM shelf list and the Home shelf-title drag. Gamepad grab (A toggles + L/R shifts via FocusNavController patch) stays in ManualSortRow.

Coexisting interaction modes

  • Side chevrons — transparent SVG; click shifts one position (touch, mouse, D-pad pressing A). Centering uses nested rAF so LEFT works the same as RIGHT.
  • Gamepad grab — focus a card + A → grabbedAppid = id; D-pad L/R shifts; A again releases.
  • Pointer hold / touchpointerdown arms a 300ms timer; on hold, pointermove hit-tests sibling cards; pointerup releases.
  • QAM up/down + extra dragReorderableShelfList carries data-ds-shelf-row={id} and uses useContainerDragReorder; up/down buttons coexist.
  • Home shelf-title dragShelvesContainer uses the same hook with itemSelector: '.ds-shelf[data-shelfid]' and allowedPointerTypes: ['mouse', 'touch'].

3.8 Crash protection

  • HomeBoundary wraps HomeShelves; on error the mount is cleared and state is published.
  • Overlay kill-switch (markReplaceFailed) — scoped to recentsReplaceSource.
  • Reset button clears the error state without re-installing.
  • All error logic stays in our scope.

3.9 Smart Shelves

Heuristic-driven shelves. 16 modes: 15 heuristic templates + custom (user filterGroup + sort, no built-in heuristic). Master toggle smartShelvesEnabled in the QAM.

SmartShelf schema: { id, title, mode, enabled, hidden, limit?, sort?, manualOrder?, manualBaseSort?, filterGroup?, matchNativeSize?, highlight*, hide*, refreshIntervalMinutes?, smartParams?, visibleHours?, visibleDaysOfWeek? }.

resolveSmartShelf(mode, apps, limit, params, ttlMs, shelfId?) in src/steam/smartShelves.ts: pure, no side effects; silent failure (returns []). Cache TTL ~5min, namespaced by shelfId for per-shelf isolation.

Surprise Me — picks N modes randomly with a daily seed (consistent within the day, no state).

smartParams: each mode exposes editable parameters (sliders / dropdown / text inputs). minDeckLevel (0-3, per-mode default) is a dropdown with localized labels; playtime params (maxPlaytimeMinutes / minPlaytimeMinutes) are text fields with a draft buffer.

Visibility windowvisibleHours: Array<{start, end}> OR-combined with wrap-around; visibleDaysOfWeek: number[] (undefined = no restriction, [] = never visible). HomeInject filters out shelves outside the window and schedules a one-shot setTimeout for the next boundary.

3.10 Internationalization

16 complete locales: en-US, pt-BR, pt-PT, es-ES, es-419, fr-FR, de-DE, it-IT, ru-RU, pl-PL, nl-NL, tr-TR, uk-UA, ja-JP, ko-KR, zh-CN. validate-compat.sh checks key consistency.

Task Checklist

Developer

  • I am the original author or an authorized maintainer of this plugin.
  • I have abided by the licenses of the libraries I am utilizing, including attaching license notices where appropriate.

Plugin

  • I have verified that my plugin works properly on the Stable and Beta update channels of SteamOS.
  • I have verified my plugin is unique or provides more/alternative functionality to a plugin already on the store.

Backend

  • No: I am using a custom backend other than Python.
  • No: I am using a tool or software from a 3rd party FOSS project that does not have it's dependencies statically linked.
  • No: I am using a custom binary that has all of it's dependencies statically linked.

Community

  • I have tested and left feedback on two other pull requests for new or updating plugins.
  • I have commented links to my testing report in this PR.

Testing

  • Tested by a third party on SteamOS Stable or Beta update channel.

@santojon santojon requested a review from a team as a code owner April 2, 2026 21:37
@github-actions github-actions Bot added the plugin-addition Adding a plugin to the Plugin Store label Apr 2, 2026
@github-actions
Copy link
Copy Markdown

github-actions Bot commented Apr 2, 2026

Issues Found

  • Both testing boxes are present in your description. Please remove one of them per the Markdown comment above the testing section.

Next Steps

  1. If we found any issues above, please edit your pull request description to resolve them and leave a comment saying you've done so.
  2. For the quickest review, please see the Community section of the pull request template for how you can help other developers.
  3. Once your description is correct, a maintainer will review your pull request as soon as possible.

Thank you for your contribution! If you need any help, please reach out on our Discord server. ❤️

@santojon
Copy link
Copy Markdown
Contributor Author

santojon commented Apr 2, 2026

debug flag exists on code just for debug purposes, but is removed in my packaging script:
https://github.com/santojon/Deck-Shelves/blob/main/scripts/build/package.sh
Lines 17-19

if have to run this script on some specific flow decky build follows, please say me

I'm using it on

pnpm run package

and

pnpm run dist

@moi952
Copy link
Copy Markdown
Contributor

moi952 commented Apr 3, 2026

Hi,
I tried your plugin — it’s really great and fills a huge gap in Steam’s functionality. However, I’m experiencing a few issues:

1- My favorites aren't displaying. I have 7 favorites, but nothing shows up on the home screen. Perhaps the selection is based on the collection name, which in my case must be French and in yours English? So the problem would occur for all languages.

2- I tried the "Recently Added" collection, but it doesn't seem to be sorted in that order.

3- The displayed covers don't use the style (rounded edges, for example). You can see that when I navigate, Steam games have a border radius when focused, but not the Steam games themselves (Cyberpunk 2077 is a non-steam game, Splitgate is a steam game).

4- When navigating the Steam row, the focus of the game I'm browsing is the middle one. If I scroll to the right, it scrolls to the left, but the selected cover is still the middle one. With your plugin, the selection goes all the way to the right before scrolling.

5- There's a sort of lag, more like a latency (but I don't know if it's Steam causing this or not) when I navigate to a line you added

6- Quite a few translations are missing

Despite these points, the plugin is really clean and works very well. I expected some asynchronous loading with a delay before collections appeared, but that’s not the case.
If you manage to make the small adjustments (especially navigation behavior and correct border rendering), this plugin will become indispensable!

20260403100307_1
20260403101139_1

@EMERALD0874
Copy link
Copy Markdown
Member

@moi952 Please only review plugins that have been deployed to the testing store.

Withholding review on this plugin since the required action mentioned by GitHub Actions has not been completed.

@moi952
Copy link
Copy Markdown
Contributor

moi952 commented Apr 3, 2026

@moi952 Please only review plugins that have been deployed to the testing store.

Withholding review on this plugin since the required action mentioned by GitHub Actions has not been completed.

Sorry, I didn't know. This is the first time I've submitted a plugin, and especially the first time I've seen how it works overall.

@beebls
Copy link
Copy Markdown
Contributor

beebls commented Apr 8, 2026

debug flag exists on code just for debug purposes, but is removed in my packaging script: https://github.com/santojon/Deck-Shelves/blob/main/scripts/build/package.sh Lines 17-19

if have to run this script on some specific flow decky build follows, please say me

I'm using it on

pnpm run package

and

pnpm run dist

Our build CI just builds from the source that is in the file at present, so you will have to remove the debug flag in source.

Additionally, it appears you have not properly linked the submodule as there is no new plugins being detected.

@EMERALD0874
Copy link
Copy Markdown
Member

@santojon Please make your submodule a path without spaces.

@EMERALD0874
Copy link
Copy Markdown
Member

debug flag exists on code just for debug purposes, but is removed in my packaging script[...]

Unless you use the backend folder to run a custom backend build process, you can't easily specify a shell script to run. I highly recommend removing the debug flag and using a shell script to build it with the flag instead.

@santojon
Copy link
Copy Markdown
Contributor Author

santojon commented Apr 9, 2026

debug flag exists on code just for debug purposes, but is removed in my packaging script[...]

Unless you use the backend folder to run a custom backend build process, you can't easily specify a shell script to run. I highly recommend removing the debug flag and using a shell script to build it with the flag instead.

Both suggestions done ✅ @EMERALD0874

@santojon
Copy link
Copy Markdown
Contributor Author

debug flag exists on code just for debug purposes, but is removed in my packaging script: https://github.com/santojon/Deck-Shelves/blob/main/scripts/build/package.sh Lines 17-19

if have to run this script on some specific flow decky build follows, please say me

I'm using it on

pnpm run package

and

pnpm run dist

Our build CI just builds from the source that is in the file at present, so you will have to remove the debug flag in source.

Additionally, it appears you have not properly linked the submodule as there is no new plugins being detected.

Have tried here and now it seems to be ok. Can you try again?

@santojon
Copy link
Copy Markdown
Contributor Author

santojon commented Apr 20, 2026

Test reports:

#1022

#1005

#1016

@santojon santojon force-pushed the submit-Deck-Shelves-v1.0.0 branch from 01f8cc0 to 41f1c6a Compare April 30, 2026 23:08
@santojon
Copy link
Copy Markdown
Contributor Author

@EMERALD0874 @beebls
I updated now to 2.0.0 and will keep development stale for a while.
Please re-review it from scratch.
I tested it on SteamOS 3.7.x to 3.9 And ensure working on beta clients too.

@beebls
Copy link
Copy Markdown
Contributor

beebls commented May 1, 2026

Before we review this I just want to get a statement on how and if AI coding tools were used in the creation of this plugin.

We can't approve plugins where AI was used to generate a majority of the code to the store due to issues around proving who owns the copyright to them. Majority is a bit of a subjective term of course, but we just want to know if you used AI coding tools during the plugin creation process, and if so, can you still vouch for both the originality, quality, and security of your code.

@santojon
Copy link
Copy Markdown
Contributor Author

santojon commented May 1, 2026

Sure, I understand your concern.

I used Github Copilot for internationalization into languages ​​other than Portuguese, English, and Spanish, which are languages ​​I don't master, to speed up documentation updates (with subsequent manual review) and the creation of diagnostic scripts and some other scripts that I revised later.

All the functional part of the plugin (everything within /src) was done without the use of AI.

Copy link
Copy Markdown
Member

@EMERALD0874 EMERALD0874 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM for testing but will review the code closer at another point

For maintainers: Initial review looks like AI was used for supplementary purposes like documentation and translation, supporting the dev's claim. I'll look into this further at another point to be sure, given certain things were clearly AI-generated (e.g., branding) when they could've been created without it.

@santojon
Copy link
Copy Markdown
Contributor Author

santojon commented May 3, 2026

Branding image was generated using Chat GPT in project's initial phase, before start the development, it was a starter point for me to visualize and ideate about how it will look, but the real project after development looks very different.

I can change or remove it if requested without problems.

@Yhangzzz12
Copy link
Copy Markdown

Plugin Testing Report

Installed Plugins

  • Decklingo - 0.0.1
  • Audio Loader - 1.61
  • ProtonDB Badges -1.2.0
  • Animation Changer - 1.3.2
  • Fantastic - 0.5.1
  • AlarMe - 1.6.3
  • Decky Translator - 0.8.0
  • Deck Shelves - 2.0.0

Specifications

  • SteamOS 3.9 (Main)
  • Steam 1775946595 (Beta)
  • Decky 3.2.3 (Stable)

Issues

Has the following major blocking issue(s): None
Has the following minor non-blocking issue(s): None, looks good to me.

Summary

Tested by creating custom shelves on the home screen and editing them via the Quick Access Menu. Custom shelves appeared correctly on the home screen below the native recents row. Steam collections loaded and displayed properly. Navigation between shelf items felt smooth and responsive. Overall a very well-built plugin that fills a gap in Steam Deck's functionality. Works as expected.

@moi952
Copy link
Copy Markdown
Contributor

moi952 commented May 5, 2026

Plugin Testing Report

Installed Plugins

Decky proton launch - 0.9.0
SteamGridDB - 1.7.1
NonSteamLaunchers - 1.3.46
Decky-Framegen - 0.14.0
Free Loader - 1.5.2
CSS Loader - 2.1.2
ProtonDB Badges - 1.2.0
Decky LSFG-VK - 0.12.2
Hydra - 0.0.3
Deck Shelves - 2.0.0-41f1c6a
UnifiDeck - 0.6.1
Games Prefix Manager - 1.0.1

Specifications

Bazzite (Steam Deck mode)
Steam 1777935142
Decky 3.2.3 (Preview)

Issues

No issues to report.

Summary

I tested the plugin under normal usage conditions and everything works as expected. No bugs or conflicts observed with other installed plugins.

Copy link
Copy Markdown
Contributor

@beebls beebls left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Gonna second the approval here, all looks good

@beebls beebls merged commit f576607 into SteamDeckHomebrew:main May 6, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

plugin-addition Adding a plugin to the Plugin Store

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants