@deck-shelves/host 0.1.0 → 1.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,20 +1,36 @@
1
1
  # @deck-shelves/host
2
2
 
3
+ <div align="center">
4
+
5
+ [![CI](https://github.com/santojon/Deck-Shelves-HOST/actions/workflows/ci.yml/badge.svg)](https://github.com/santojon/Deck-Shelves-HOST/actions/workflows/ci.yml)
6
+ [![Release](https://github.com/santojon/Deck-Shelves-HOST/actions/workflows/release.yml/badge.svg)](https://github.com/santojon/Deck-Shelves-HOST/actions/workflows/release.yml)
7
+ [![npm version](https://img.shields.io/npm/v/@deck-shelves/host?logo=npm&color=cb3837)](https://www.npmjs.com/package/@deck-shelves/host)
8
+ [![npm downloads](https://img.shields.io/npm/dt/@deck-shelves/host?label=downloads&logo=npm&color=blue)](https://www.npmjs.com/package/@deck-shelves/host)
9
+ [![npm monthly](https://img.shields.io/npm/dm/@deck-shelves/host?label=monthly&logo=npm&color=blue)](https://www.npmjs.com/package/@deck-shelves/host)
10
+ [![Tests](https://img.shields.io/badge/tests-2%20passed-brightgreen?logo=vitest&logoColor=white)](src/contract/index.test.ts)
11
+ [![Types](https://img.shields.io/npm/types/@deck-shelves/host?logo=typescript&logoColor=white)](src/contract/index.ts)
12
+ [![Bundle size](https://img.shields.io/bundlephobia/minzip/@deck-shelves/host?label=minzip&color=blue)](https://bundlephobia.com/package/@deck-shelves/host)
13
+ [![License](https://img.shields.io/npm/l/@deck-shelves/host?color=blue)](LICENSE)
14
+ [![Node](https://img.shields.io/node/v/@deck-shelves/host?logo=node.js&logoColor=white)](package.json)
15
+ [![Platform](https://img.shields.io/badge/platform-SteamOS%20%C2%B7%20Linux%20%C2%B7%20Windows-purple?logo=steamdeck&logoColor=white)](https://github.com/ValveSoftware/SteamOS)
16
+ [![Deck Shelves](https://img.shields.io/badge/host-Deck%20Shelves-purple)](https://github.com/santojon/Deck-Shelves)
17
+ [![Sponsor](https://img.shields.io/badge/Sponsor-GitHub-ea4aaa?logo=github&logoColor=white)](https://github.com/sponsors/santojon)
18
+ [![Ko-fi](https://img.shields.io/badge/Support%20me%20on%20Ko--fi-F16061?logo=ko-fi&logoColor=white)](https://ko-fi.com/santojon)
19
+
20
+ </div>
21
+
3
22
  The **host contract** for [Deck Shelves](https://github.com/santojon/Deck-Shelves) —
4
23
  the `HostApi` types that both host implementations and the bundle build against.
5
24
 
6
- It is the boundary between a *host* and the Deck Shelves bundle:
7
-
8
- - the plugin's **Decky** adapter (`runtime/host/decky.ts`), and
9
- - the standalone adapter (`runtime/host/standalone.ts`),
10
- which wraps the loader-injected `window.__SHELVES_HOST__` runtime.
11
-
12
- Both fulfil the same contract, so the bundle's call sites never depend on a
13
- specific host.
25
+ It is the boundary between a *host* and the Deck Shelves bundle. Each host
26
+ ships its own adapter in the plugin — one file per host,
27
+ `runtime/host/<host>.ts` — and every adapter fulfils this same contract, so
28
+ the bundle's call sites never depend on a specific host and new hosts can be
29
+ added without touching the bundle.
14
30
 
15
- > **Types only.** The standalone host *runtime* — the injected
31
+ > **Types only.** A host's *runtime* — for external hosts, the injected
16
32
  > `window.__SHELVES_HOST__` that locates Steam's UI components and adds the
17
- > Quick Access Menu tab — lives in the **Shelves Loader**, not here. This
33
+ > Quick Access Menu tab — lives in that host's own project, not here. This
18
34
  > package is just the interface both sides agree on.
19
35
 
20
36
  It is **not** `@deck-shelves/api` — that package is the public *extension* API
package/dist/index.d.cts CHANGED
@@ -1,18 +1,19 @@
1
1
  /**
2
- * @deck-shelves/host — HostApi contract (v1.1.0).
2
+ * @deck-shelves/host — HostApi contract.
3
3
  *
4
- * The single source of truth for the boundary between a *host* (the plugin's
5
- * host adapter OR the standalone Shelves Loader) and the Deck Shelves bundle.
6
- * Both host adapters fulfil this shape, so the bundle's call sites depend only
7
- * on it and never on a specific host's UI library directly.
4
+ * The single source of truth for the boundary between a *host* and the Deck
5
+ * Shelves bundle. Every host ships its own adapter (one per host), and every
6
+ * adapter fulfils this shape, so the bundle's call sites depend only on it
7
+ * and never on a specific host's UI library directly.
8
8
  *
9
- * Additive-only after 1.0.0. `qam` was added in 1.1.0 as an optional, additive
10
- * namespace (only the standalone host implements it today).
9
+ * Additive-only Optional UI-surface capabilities are additive
10
+ * namespaces a host implements *if it can* and the bundle feature-detects:
11
+ * - `qam` — a Quick Access Menu tab.
12
+ * - `mainMenu` — an entry in the Steam Main Menu / left rail.
13
+ * The contract names no concrete host; a host may implement any, all, or none.
11
14
  *
12
15
  * Dependency-free by design (mirrors `@deck-shelves/api`): supporting data
13
- * types are inlined. Members annotated "reconciled in Batch 5" are placeholders
14
- * that get tightened against the plugin's `src/types.ts` / `runtime/platform.ts`
15
- * when the contract formally supersedes the two divergent in-repo copies.
16
+ * types are inlined.
16
17
  */
17
18
  declare const HOST_API_VERSION: "1.1.0";
18
19
  interface PluginDescriptor {
@@ -28,9 +29,9 @@ interface HostLifecycle {
28
29
  onMount(cb: () => void): void;
29
30
  onUnmount(cb: () => void): void;
30
31
  }
31
- /** Generic RPC channel into the host backend. On the plugin host this routes
32
- * through the host's backend bridge; on standalone through the loader's HTTP
33
- * RPC server, which proxies to the plugin's Python backend. */
32
+ /** Generic RPC channel into the host backend. Each host routes it to the
33
+ * plugin's data backend through its own transport (an in-process bridge, a
34
+ * local HTTP server that proxies to the backend process, …). */
34
35
  interface HostRpc {
35
36
  call<Req = unknown, Res = unknown>(method: string, args?: Req): Promise<Res>;
36
37
  }
@@ -47,10 +48,10 @@ interface HostNotifications {
47
48
  toast(opts: ToastOptions): void;
48
49
  }
49
50
  /**
50
- * Steam UI primitives the bundle renders with. On the plugin host these come
51
- * from its UI library; on the standalone host the injected runtime locates the
52
- * SAME Steam webpack components (via the Shelves Loader's injected runtime) — we do not
53
- * reimplement the widgets, we find Steam's own and provide fallbacks.
51
+ * Steam UI primitives the bundle renders with. Every host resolves the SAME
52
+ * Steam webpack components through its own mechanism (a UI library, an
53
+ * injected runtime, …) — we do not reimplement the widgets, we find Steam's
54
+ * own and provide fallbacks.
54
55
  *
55
56
  * Typed as `unknown` to stay framework-agnostic and dependency-free; the bundle
56
57
  * casts each to its React component / handler type at the call site.
@@ -76,7 +77,6 @@ interface HostUi {
76
77
  showContextMenu: (menu: unknown) => void;
77
78
  showModal: (modal: unknown) => void;
78
79
  }
79
- /** Opaque until reconciled in Batch 5 with the plugin's `ShelfSource` union. */
80
80
  type ShelfSource = unknown;
81
81
  interface PlatformCollection {
82
82
  id: string;
@@ -118,7 +118,7 @@ interface PlatformApi {
118
118
  getAppMetaBatch?(appids: number[]): Promise<Map<number, PlatformAppMeta>>;
119
119
  navigateToApp(appid: number): void;
120
120
  navigateToShelfSource?(source: ShelfSource, title?: string): void;
121
- /** Host/OS info folded in from the standalone loader's original contract. */
121
+ /** Optional host/OS information a host may expose. */
122
122
  getOSVersion?(): string;
123
123
  checkCompatibility?(): boolean;
124
124
  }
@@ -134,14 +134,46 @@ interface QamPanel {
134
134
  render(container: HTMLElement): void | (() => void);
135
135
  }
136
136
  interface HostQam {
137
- /** Register a QAM panel; returns an unregister function. */
137
+ /**
138
+ * Register a QAM panel; returns an unregister function. A host may keep its
139
+ * QAM tab visible at all times (a first-class presence), even before any
140
+ * panel is registered.
141
+ */
138
142
  registerPanel(panel: QamPanel): () => void;
139
143
  }
144
+ /**
145
+ * An entry in the Steam Main Menu (the left navigation rail). Navigational by
146
+ * nature: it selects a route or runs a callback (unlike a QAM panel, which
147
+ * renders content in place).
148
+ */
149
+ interface MainMenuEntry {
150
+ /** Stable id — re-registering with the same id replaces the entry. */
151
+ id: string;
152
+ /** Label / accessible name. */
153
+ title: string;
154
+ /** Inline SVG markup (or a `data:` URI) used as the icon. */
155
+ icon: string;
156
+ /** Navigate to this registered route on selection… */
157
+ route?: string;
158
+ /** …or run this callback (exactly one of `route` / `onSelect` is required). */
159
+ onSelect?(): void;
160
+ }
161
+ interface HostMainMenu {
162
+ /**
163
+ * Register a Main Menu entry; returns an unregister function. Unlike the QAM
164
+ * tab, a host MUST NOT inject or show a Main Menu entry unless there is
165
+ * content to show — i.e. only while at least one entry is registered. No
166
+ * entries → nothing added to the Main Menu.
167
+ */
168
+ registerEntry(entry: MainMenuEntry): () => void;
169
+ }
140
170
  /**
141
171
  * What the host process provides to the Deck Shelves bundle. The bundle receives
142
172
  * this at startup as `window.__SHELVES_HOST__` and uses it to register itself,
143
- * invoke host methods, add routes, render Steam-native UI, and (on the
144
- * standalone host) add Quick Access Menu panels.
173
+ * invoke host methods, add routes, render Steam-native UI, and — where the host
174
+ * supports them — add optional surfaces (a Quick Access Menu tab, a Main Menu
175
+ * entry). Optional capabilities are feature-detected: `host.qam?.…`,
176
+ * `host.mainMenu?.…`.
145
177
  */
146
178
  interface HostApi {
147
179
  readonly version: typeof HOST_API_VERSION;
@@ -153,6 +185,9 @@ interface HostApi {
153
185
  readonly platform: PlatformApi;
154
186
  /** Optional so hosts without a QAM surface still satisfy the shape. */
155
187
  readonly qam?: HostQam;
188
+ /** Optional Main Menu (left-rail) surface; present only on hosts that
189
+ * support it. Injected/shown only while it has content (see `HostMainMenu`). */
190
+ readonly mainMenu?: HostMainMenu;
156
191
  }
157
192
  /** Shape of the runtime global the host installs in the renderer. */
158
193
  type ShelvesHostGlobal = HostApi;
@@ -162,4 +197,4 @@ declare global {
162
197
  }
163
198
  }
164
199
 
165
- export { type Disposable, HOST_API_VERSION, type HostApi, type HostLifecycle, type HostNotifications, type HostQam, type HostRoutes, type HostRpc, type HostUi, type PlatformApi, type PlatformAppMeta, type PlatformCollection, type PlatformTab, type PluginDescriptor, type QamPanel, type ShelfSource, type ShelvesHostGlobal, type ToastOptions };
200
+ export { type Disposable, HOST_API_VERSION, type HostApi, type HostLifecycle, type HostMainMenu, type HostNotifications, type HostQam, type HostRoutes, type HostRpc, type HostUi, type MainMenuEntry, type PlatformApi, type PlatformAppMeta, type PlatformCollection, type PlatformTab, type PluginDescriptor, type QamPanel, type ShelfSource, type ShelvesHostGlobal, type ToastOptions };
package/dist/index.d.ts CHANGED
@@ -1,18 +1,19 @@
1
1
  /**
2
- * @deck-shelves/host — HostApi contract (v1.1.0).
2
+ * @deck-shelves/host — HostApi contract.
3
3
  *
4
- * The single source of truth for the boundary between a *host* (the plugin's
5
- * host adapter OR the standalone Shelves Loader) and the Deck Shelves bundle.
6
- * Both host adapters fulfil this shape, so the bundle's call sites depend only
7
- * on it and never on a specific host's UI library directly.
4
+ * The single source of truth for the boundary between a *host* and the Deck
5
+ * Shelves bundle. Every host ships its own adapter (one per host), and every
6
+ * adapter fulfils this shape, so the bundle's call sites depend only on it
7
+ * and never on a specific host's UI library directly.
8
8
  *
9
- * Additive-only after 1.0.0. `qam` was added in 1.1.0 as an optional, additive
10
- * namespace (only the standalone host implements it today).
9
+ * Additive-only Optional UI-surface capabilities are additive
10
+ * namespaces a host implements *if it can* and the bundle feature-detects:
11
+ * - `qam` — a Quick Access Menu tab.
12
+ * - `mainMenu` — an entry in the Steam Main Menu / left rail.
13
+ * The contract names no concrete host; a host may implement any, all, or none.
11
14
  *
12
15
  * Dependency-free by design (mirrors `@deck-shelves/api`): supporting data
13
- * types are inlined. Members annotated "reconciled in Batch 5" are placeholders
14
- * that get tightened against the plugin's `src/types.ts` / `runtime/platform.ts`
15
- * when the contract formally supersedes the two divergent in-repo copies.
16
+ * types are inlined.
16
17
  */
17
18
  declare const HOST_API_VERSION: "1.1.0";
18
19
  interface PluginDescriptor {
@@ -28,9 +29,9 @@ interface HostLifecycle {
28
29
  onMount(cb: () => void): void;
29
30
  onUnmount(cb: () => void): void;
30
31
  }
31
- /** Generic RPC channel into the host backend. On the plugin host this routes
32
- * through the host's backend bridge; on standalone through the loader's HTTP
33
- * RPC server, which proxies to the plugin's Python backend. */
32
+ /** Generic RPC channel into the host backend. Each host routes it to the
33
+ * plugin's data backend through its own transport (an in-process bridge, a
34
+ * local HTTP server that proxies to the backend process, …). */
34
35
  interface HostRpc {
35
36
  call<Req = unknown, Res = unknown>(method: string, args?: Req): Promise<Res>;
36
37
  }
@@ -47,10 +48,10 @@ interface HostNotifications {
47
48
  toast(opts: ToastOptions): void;
48
49
  }
49
50
  /**
50
- * Steam UI primitives the bundle renders with. On the plugin host these come
51
- * from its UI library; on the standalone host the injected runtime locates the
52
- * SAME Steam webpack components (via the Shelves Loader's injected runtime) — we do not
53
- * reimplement the widgets, we find Steam's own and provide fallbacks.
51
+ * Steam UI primitives the bundle renders with. Every host resolves the SAME
52
+ * Steam webpack components through its own mechanism (a UI library, an
53
+ * injected runtime, …) — we do not reimplement the widgets, we find Steam's
54
+ * own and provide fallbacks.
54
55
  *
55
56
  * Typed as `unknown` to stay framework-agnostic and dependency-free; the bundle
56
57
  * casts each to its React component / handler type at the call site.
@@ -76,7 +77,6 @@ interface HostUi {
76
77
  showContextMenu: (menu: unknown) => void;
77
78
  showModal: (modal: unknown) => void;
78
79
  }
79
- /** Opaque until reconciled in Batch 5 with the plugin's `ShelfSource` union. */
80
80
  type ShelfSource = unknown;
81
81
  interface PlatformCollection {
82
82
  id: string;
@@ -118,7 +118,7 @@ interface PlatformApi {
118
118
  getAppMetaBatch?(appids: number[]): Promise<Map<number, PlatformAppMeta>>;
119
119
  navigateToApp(appid: number): void;
120
120
  navigateToShelfSource?(source: ShelfSource, title?: string): void;
121
- /** Host/OS info folded in from the standalone loader's original contract. */
121
+ /** Optional host/OS information a host may expose. */
122
122
  getOSVersion?(): string;
123
123
  checkCompatibility?(): boolean;
124
124
  }
@@ -134,14 +134,46 @@ interface QamPanel {
134
134
  render(container: HTMLElement): void | (() => void);
135
135
  }
136
136
  interface HostQam {
137
- /** Register a QAM panel; returns an unregister function. */
137
+ /**
138
+ * Register a QAM panel; returns an unregister function. A host may keep its
139
+ * QAM tab visible at all times (a first-class presence), even before any
140
+ * panel is registered.
141
+ */
138
142
  registerPanel(panel: QamPanel): () => void;
139
143
  }
144
+ /**
145
+ * An entry in the Steam Main Menu (the left navigation rail). Navigational by
146
+ * nature: it selects a route or runs a callback (unlike a QAM panel, which
147
+ * renders content in place).
148
+ */
149
+ interface MainMenuEntry {
150
+ /** Stable id — re-registering with the same id replaces the entry. */
151
+ id: string;
152
+ /** Label / accessible name. */
153
+ title: string;
154
+ /** Inline SVG markup (or a `data:` URI) used as the icon. */
155
+ icon: string;
156
+ /** Navigate to this registered route on selection… */
157
+ route?: string;
158
+ /** …or run this callback (exactly one of `route` / `onSelect` is required). */
159
+ onSelect?(): void;
160
+ }
161
+ interface HostMainMenu {
162
+ /**
163
+ * Register a Main Menu entry; returns an unregister function. Unlike the QAM
164
+ * tab, a host MUST NOT inject or show a Main Menu entry unless there is
165
+ * content to show — i.e. only while at least one entry is registered. No
166
+ * entries → nothing added to the Main Menu.
167
+ */
168
+ registerEntry(entry: MainMenuEntry): () => void;
169
+ }
140
170
  /**
141
171
  * What the host process provides to the Deck Shelves bundle. The bundle receives
142
172
  * this at startup as `window.__SHELVES_HOST__` and uses it to register itself,
143
- * invoke host methods, add routes, render Steam-native UI, and (on the
144
- * standalone host) add Quick Access Menu panels.
173
+ * invoke host methods, add routes, render Steam-native UI, and — where the host
174
+ * supports them — add optional surfaces (a Quick Access Menu tab, a Main Menu
175
+ * entry). Optional capabilities are feature-detected: `host.qam?.…`,
176
+ * `host.mainMenu?.…`.
145
177
  */
146
178
  interface HostApi {
147
179
  readonly version: typeof HOST_API_VERSION;
@@ -153,6 +185,9 @@ interface HostApi {
153
185
  readonly platform: PlatformApi;
154
186
  /** Optional so hosts without a QAM surface still satisfy the shape. */
155
187
  readonly qam?: HostQam;
188
+ /** Optional Main Menu (left-rail) surface; present only on hosts that
189
+ * support it. Injected/shown only while it has content (see `HostMainMenu`). */
190
+ readonly mainMenu?: HostMainMenu;
156
191
  }
157
192
  /** Shape of the runtime global the host installs in the renderer. */
158
193
  type ShelvesHostGlobal = HostApi;
@@ -162,4 +197,4 @@ declare global {
162
197
  }
163
198
  }
164
199
 
165
- export { type Disposable, HOST_API_VERSION, type HostApi, type HostLifecycle, type HostNotifications, type HostQam, type HostRoutes, type HostRpc, type HostUi, type PlatformApi, type PlatformAppMeta, type PlatformCollection, type PlatformTab, type PluginDescriptor, type QamPanel, type ShelfSource, type ShelvesHostGlobal, type ToastOptions };
200
+ export { type Disposable, HOST_API_VERSION, type HostApi, type HostLifecycle, type HostMainMenu, type HostNotifications, type HostQam, type HostRoutes, type HostRpc, type HostUi, type MainMenuEntry, type PlatformApi, type PlatformAppMeta, type PlatformCollection, type PlatformTab, type PluginDescriptor, type QamPanel, type ShelfSource, type ShelvesHostGlobal, type ToastOptions };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@deck-shelves/host",
3
- "version": "0.1.0",
3
+ "version": "1.1.1",
4
4
  "description": "Deck Shelves host contract — the HostApi types all host adapters and the bundle build against.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",
@@ -56,7 +56,8 @@
56
56
  },
57
57
  "homepage": "https://github.com/santojon/Deck-Shelves-HOST#readme",
58
58
  "publishConfig": {
59
- "access": "public"
59
+ "access": "public",
60
+ "provenance": true
60
61
  },
61
62
  "pnpm": {
62
63
  "onlyBuiltDependencies": [