All notable technical changes to this project are documented here.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
- Canonical filter-tree types
PublicFilterGroup/PublicFilterItem(a group combines items withand/or; each item names a filter type, may beinverted, and carriesparams) andParsedImport(the normalized result of parsing an import file into shelves / smart shelves). These are now exported so consumers share one definition instead of re-declaring their own. PublicSavedFilter.groupand the fullPublicSavedSmartFilterprojection (mode,smartParams,filterGroup,sort,sortReverse,limit,visibleHours,visibleDaysOfWeek) — the saved-filter getters already return these fields; the types now describe them.PublicSmartShelf.sortadded for the same reason. All additive (optional fields).
- Contract now matches what the host actually emits / calls (corrects types
that never reflected the runtime — code that relied on the old shapes was
already broken at run time):
PublicShelfSourcecollection variant is{ type: "collection"; collectionId: string }(wascollection), and thefiltervariant is typed{ sort?: string; group?: PublicFilterGroup }(wasunknown).SmartShelfSourceDescriptor.resolveis(limit, params?) => number[] | Promise<number[]>(was(apps, limit, params) => number[]);defaultParams/ theparamsargument areRecord<string, number>.
- Built-in catalogue discovery:
listTriggerCatalog(),listShelfTemplates()andlistShortcuts(), with the newPublicTriggerKind,PublicShelfTemplateandPublicShortcuttypes. Read-only — integrations can build on the same trigger kinds, templates and gamepad shortcuts Deck Shelves ships. EnvironmentInfo.os—getEnvironment()now reports a best-effort host OS ("SteamOS"|"Windows"|"macOS"|"Linux";undefinedwhen it can't be determined), so integrations can branch per platform. Additive (optional field).PublicTriggerKind.categorynow documents"peripherals"(the controller / headphones / Bluetooth-device trigger kinds) alongsidetime/session/power/connectivity/display/perf.
- Export / import handler descriptors (additive; v4 consumers stay binary-compatible). New
ExportHandlerDescriptor/ImportHandlerDescriptortypes +registerExportHandler/registerImportHandler(+getRegisteredExportHandlers/getRegisteredImportHandlers) onDeckShelvesPublicAPI(src/types.ts). A plugin offers "Export to format X" / "Import from format Y" by translating between the host's snapshot JSON — a serialized bundle of shelves, smart shelves, saved filters and saved smart filters — and its own format. Both sides exchange the snapshot as a JSON string, so no host types leak and the round-trip stays lossless:export(snapshotJson)returns your format's text;import(raw)returns a snapshot JSON string the host applies. Feature-detect (typeof api.registerExportHandler === "function") until tagged.
form-datapinned to^4.0.6via a pnpmoverridesentry (package.json) — resolves GHSA-hmw2-7cc7-3qxx / CVE-2026-12143 (high: CRLF injection via unescaped multipart field names / filenames), a transitive dev-dependency pulled in by the test toolchain (jsdom).pnpm auditis clean. No runtime or consumer impact —form-datais never bundled into the published package.pnpm run update/update:check(package.json) — refresh every dev-dependency to latest (pnpm update --latest && pnpm install) or preview outdated ones (pnpm outdated), so the package can be kept current standalone (it ships from its ownDeck-Shelves-APIrepo).- Expanded test coverage (
src/index.test.ts, 6 → 14 cases) — addedonReady(microtask path, event path, single-fire, unsubscribe, error-swallow) andonTeardown(multi-fire, unsubscribe, error-swallow) suites.
- Profiles + integrations getters on
DeckShelvesPublicAPI(additive). New methods:getProfiles(),getActiveProfile(),subscribeProfiles(cb),getIntegrations(),subscribeIntegrations(cb). New public typesPublicProfile(id,name,createdAt,active) andIntegrationInfo(id,displayName,installed,enabled) projected over the host'sprofiles/integrationsEnabledsettings fields. Known integrations the host detects:tabmaster,unifideck,nonsteambadges. - Statistics + recommendation provider descriptors (additive). New
StatisticsProviderDescriptor/RecommendationProviderDescriptor+StatisticsEntry/RecommendationEntrytypes. Companionregister*Provider+getRegistered*Providersmethods mirror the existing widget / context / shelf-renderer pattern. Statistics providers expose key/value entries with optional grouping; recommendation providers rank appids with an optional reason string. - Settings snapshot + environment probe (additive).
getSettingsSnapshot()returns aPublicSettingsSnapshot(top-level toggles +integrationsEnabled+featureToggles+activeProfileName);subscribeSettingsSnapshot(cb)subscribes to diffs.getEnvironment()returns{ pluginVersion, apiVersion, locale, isGamepadUi }. - Lifecycle helpers exported from the npm package (
api/src/index.ts).onReady(cb: (api) => void): Unsubscribefires once when DS is ready (or on next microtask if already loaded).onTeardown(cb: () => void): Unsubscribefires whenever the host unloads. Both wrap the existingdeck-shelves:ready/deck-shelves:teardownwindow events withaddEventListenercleanup so consumers don't have to wire them manually. - API v4 — 4 new descriptor types (additive, v3 consumers stay binary-compatible).
DeckShelvesPublicAPI.versionbumped3 → 4. v4 was never split into a separate v4 / v5 release: every additive surface that had been queued (search + side-menu providers AND context / widget / shelf-renderer / metadata providers) rolls up into this single bump. ContextProviderDescriptor+registerContextProvider/getRegisteredContextProviders. Companion plugins expose context signals (Bluetooth headset connected, specific game running, custom session predicates) viasnapshot()+subscribe(cb). Host consumes for shelf visibility rules and profile auto-switch.WidgetProviderDescriptor+registerWidgetProvider/getRegisteredWidgetProviders. Runtime widgets that render non-game content.render({ width, height }); optionalrefreshPolicy: number | "focus" | null; optionalskeleton()for first-paint.ShelfRendererDescriptor+registerShelfRenderer/getRegisteredShelfRenderers. External render modes for the per-shelfrenderModedropdown.layout({ items, focusedAppid, cardWidth, cardHeight, featured }); optionalcardMode+virtualiseAfter.MetadataProviderDescriptor+registerMetadataProvider/getRegisteredMetadataProviders. Augments app metadata (ratings, playtime estimates, completion %, achievement counts, emulator tags).fields: ReadonlyArray<string>advertises what the provider populates;resolve(appids, signal)batches lookups with host-cancellation support.- Built-in Quick Search now lives in the same
searchProvidersregistry external plugins write to. No surface change for consumers of@deck-shelves/api— the descriptor (id: "deck-shelves.shelves",priority: 100) is registered through the host's internal-bootstrap path. Effect on external plugins:api.getRegisteredSearchProviders()returns the built-in alongside any provider your plugin registered, sorted by priority desc. Collision detection works for the built-in id the same way as for any other registration. pnpm run upgradescript (api/package.json) + self-contained helper atscripts/upgrade-pnpm.cjs. Mirrors the helper at the plugin repo root so the API package stays usable by downstream contributors who clone only theDeck-Shelves-APIrepo (which is the published surface — separate from the plugin). 4-step fallback chain:pnpm self-update(covers Homebrew / asdf / npm installs in-place), Corepack from PATH ornode's bundled location,brew upgrade pnpm, thennpm install -g pnpm@latest --force. The parent repo'spnpm run upgrade:apinow delegates to this self-contained script (was previously a thin wrapper that loaded../scripts/build/upgrade-pnpm.cjsfrom the plugin tree).- README rewrite (
api/README.md). Now mirrors the main Deck Shelves README layout: centered logo-block, comprehensive badge bar (npm version + total / monthly downloads, types, bundle size via bundlephobia, CI + release workflows, license, Node compat, platform, plugin host, sponsor + Ko-fi), and an 11-row Capability matrix mapping every surface (regular + smart shelf sources, filter types, sort options, importers/exporters, saved filters, search providers, side-menu providers, focus tracking, asset URLs, env probes) to itsregister*/ snapshot / subscribe method names. Existing how-it-works + direct-API + version-policy + development sections preserved. - Surface v4 — Search providers (part of the same v3 → v4 bump above). New
registerSearchProvider(d: SearchProviderDescriptor)registration method onDeckShelvesPublicAPI, plusgetRegisteredSearchProviders()accessor. Descriptor shape:{ id, displayName, priority?, search(query, limit): Promise<SearchHit[]> }.SearchHit:{ id, appid?, title?, subtitle?, score?, onActivate?() }. Providers ranked byprioritydesc; ties broken byscoredesc. Hits with bothappidandonActivatepreferonActivateso custom routing wins. - Surface v4 — Side-menu providers (part of the same v3 → v4 bump above). New
registerSideMenuProvider(d: SideMenuProviderDescriptor)+getRegisteredSideMenuProviders(). Descriptor:{ id, displayName, resolve(context: SideMenuContext): SideMenuEntry[] | Promise<SideMenuEntry[]> }.SideMenuContext:{ shelfId, focusedAppid }.SideMenuEntry:{ id, label, category?, icon?, disabled?, onActivate() }. Entries are grouped under the providing plugin's section in the host's dpad-left side panel. @deck-shelves/api: new descriptor types exported.SearchProviderDescriptor,SearchHit,SideMenuProviderDescriptor,SideMenuContext,SideMenuEntry. Existing v3 consumers see no behaviour change; the new methods are only callable after upgrading the type package.
- Both surfaces are additive — existing v3 consumers stay binary-compatible. Check
api.version >= 4before calling the new methods. - Stable
idprefixes recommended (my-plugin.foo) so two plugins can't collide on the same descriptor.
- pnpm-based tooling: ESLint (bug-catcher rules), Vitest, and a
tsupdual ESM + CJS +.d.tsbuild. - GitHub Actions:
ci.yml(typecheck/lint/test/build/pack),release.yml(npm publish with provenance + GitHub Release),bump.yml(PR-title-driven version bump), andpr-title.yml. - Project docs:
CONTRIBUTING.md,SECURITY.md,CODE_OF_CONDUCT.md, PR template, and issue templates.
- Build now emits a real ESM (
dist/index.js) and CJS (dist/index.cjs) bundle plus bundled type declarations, matching theexportsmap. The previous two-tsc-pass setup overwrote the ESM output with CJS and never produced the.mjsfile referenced inpackage.json.
- Initial public API:
register(),getApi(),isReady(), and theDeckShelvesPublicAPItype contract.