@pollen-robotics/reachy-mini-sdk 1.8.0 → 1.8.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/APP_CREATION_GUIDE.md +1795 -0
- package/README.md +2 -5
- package/dist/animation/distance.d.ts +87 -0
- package/dist/animation/distance.d.ts.map +1 -0
- package/dist/animation/distance.js +140 -0
- package/dist/animation/distance.js.map +1 -0
- package/dist/animation/index.d.ts +34 -0
- package/dist/animation/index.d.ts.map +1 -0
- package/dist/animation/index.js +33 -0
- package/dist/animation/index.js.map +1 -0
- package/dist/animation/pose.d.ts +40 -0
- package/dist/animation/pose.d.ts.map +1 -0
- package/dist/animation/pose.js +15 -0
- package/dist/animation/pose.js.map +1 -0
- package/dist/animation/presets.d.ts +85 -0
- package/dist/animation/presets.d.ts.map +1 -0
- package/dist/animation/presets.js +82 -0
- package/dist/animation/presets.js.map +1 -0
- package/dist/animation/safe-return.d.ts +90 -0
- package/dist/animation/safe-return.d.ts.map +1 -0
- package/dist/animation/safe-return.js +123 -0
- package/dist/animation/safe-return.js.map +1 -0
- package/dist/lib/reachy-mini.d.ts +99 -0
- package/dist/lib/reachy-mini.d.ts.map +1 -1
- package/dist/lib/reachy-mini.js +314 -9
- package/dist/lib/reachy-mini.js.map +1 -1
- package/dist/lib/types.d.ts +27 -0
- package/dist/lib/types.d.ts.map +1 -1
- package/host/README.md +14 -12
- package/host/dist/ReachyHost.d.ts +2 -1
- package/host/dist/ReachyHost.d.ts.map +1 -1
- package/host/dist/chunks/index-lMs2sCXG.js +439 -0
- package/host/dist/chunks/{mountHost-D7cgkU9Q.js → mountHost-DjE5zE9R.js} +11142 -11014
- package/host/dist/chunks/{reachy-mini-B1hlBmDQ.js → reachy-mini-GHNS4GSp.js} +494 -307
- package/host/dist/components/PickerView.d.ts.map +1 -1
- package/host/dist/components/PostOAuthSplash.d.ts +25 -0
- package/host/dist/components/PostOAuthSplash.d.ts.map +1 -0
- package/host/dist/components/ReachyHostShell.d.ts.map +1 -1
- package/host/dist/components/TopBar.d.ts +8 -1
- package/host/dist/components/TopBar.d.ts.map +1 -1
- package/host/dist/components/WelcomeBackOverlay.d.ts.map +1 -1
- package/host/dist/embed/index.d.ts +3 -2
- package/host/dist/embed/index.d.ts.map +1 -1
- package/host/dist/entry/auto.js +2 -2
- package/host/dist/entry/embed.js +2 -2
- package/host/dist/hooks/useHostBridge.d.ts +6 -0
- package/host/dist/hooks/useHostBridge.d.ts.map +1 -1
- package/host/dist/hooks/useSdk.d.ts.map +1 -1
- package/host/dist/index.js +3 -3
- package/host/dist/lib/protocol.d.ts +17 -3
- package/host/dist/lib/protocol.d.ts.map +1 -1
- package/host/dist/lib/settings.d.ts +3 -2
- package/host/dist/lib/settings.d.ts.map +1 -1
- package/host/dist/mountHost.d.ts.map +1 -1
- package/host/dist/ui/design/IdentityChipBar.d.ts +46 -0
- package/host/dist/ui/design/IdentityChipBar.d.ts.map +1 -0
- package/host/dist/ui/design/LinkQualityBars.d.ts +32 -0
- package/host/dist/ui/design/LinkQualityBars.d.ts.map +1 -0
- package/host/dist/ui/design/MetaPill.d.ts +41 -0
- package/host/dist/ui/design/MetaPill.d.ts.map +1 -0
- package/host/dist/ui/design/TransportChip.d.ts +39 -0
- package/host/dist/ui/design/TransportChip.d.ts.map +1 -0
- package/package.json +7 -4
- package/CHANGELOG.md +0 -188
- package/host/APP_AUTHOR_GUIDE.md +0 -646
- package/host/SPEC.md +0 -618
- package/host/dist/chunks/index-CyLPysJS.js +0 -400
package/host/APP_AUTHOR_GUIDE.md
DELETED
|
@@ -1,646 +0,0 @@
|
|
|
1
|
-
# Build a Reachy Mini app
|
|
2
|
-
|
|
3
|
-
How to ship a Hugging Face Space that runs on a Reachy Mini robot,
|
|
4
|
-
using `@pollen-robotics/reachy-mini-sdk/host` for OAuth, robot picking, session
|
|
5
|
-
lifecycle, and a top bar - so your code stays focused on **your
|
|
6
|
-
app's UI** and nothing else.
|
|
7
|
-
|
|
8
|
-
| Doc | Purpose | Audience |
|
|
9
|
-
|------------|--------------------------------------|-----------------------------------|
|
|
10
|
-
| README | Quick tour of the package | First-time visitors |
|
|
11
|
-
| **This** | **Step-by-step app author guide** | **You, building a new app** |
|
|
12
|
-
| SPEC.md | Behavioural contract + invariants | Maintainers, reviewers |
|
|
13
|
-
|
|
14
|
-
## Table of contents
|
|
15
|
-
|
|
16
|
-
1. [What you get for free](#1-what-you-get-for-free)
|
|
17
|
-
2. [Quickstart: clone a reference app](#2-quickstart-clone-a-reference-app)
|
|
18
|
-
3. [The 4-file contract](#3-the-4-file-contract)
|
|
19
|
-
4. [`mountHost()` API](#4-mounthost-api)
|
|
20
|
-
5. [`connectToHost()` API](#5-connecttohost-api)
|
|
21
|
-
6. [Visual identity: icon, name, emoji](#6-visual-identity-icon-name-emoji)
|
|
22
|
-
7. [Receiving an external config (deep-link, mobile)](#7-receiving-an-external-config)
|
|
23
|
-
8. [Cleaning up on leave](#8-cleaning-up-on-leave)
|
|
24
|
-
9. [Local dev: HF token vs OAuth redirect](#9-local-dev)
|
|
25
|
-
10. [Deploying to Hugging Face Spaces](#10-deploying-to-hugging-face-spaces)
|
|
26
|
-
11. [FAQ and common pitfalls](#11-faq-and-common-pitfalls)
|
|
27
|
-
|
|
28
|
-
---
|
|
29
|
-
|
|
30
|
-
## 1. What you get for free
|
|
31
|
-
|
|
32
|
-
By integrating `@pollen-robotics/reachy-mini-sdk/host`, your app **does not have to
|
|
33
|
-
write**:
|
|
34
|
-
|
|
35
|
-
- **Hugging Face OAuth**: sign-in screen, redirect handling,
|
|
36
|
-
token storage, sign-out menu - all in the host shell.
|
|
37
|
-
- **Robot discovery and picker**: list of online robots, live
|
|
38
|
-
online/offline/busy updates, click-to-pick.
|
|
39
|
-
- **Connection overlay**: the 3-step "Connecting / Starting
|
|
40
|
-
session / Waking up" view rendered on top of your iframe.
|
|
41
|
-
- **End-session button and tear-down**: a "Back to apps" affordance
|
|
42
|
-
in the top bar that cleanly closes the WebRTC session.
|
|
43
|
-
- **Dark / light theme switching**: respected from
|
|
44
|
-
`prefers-color-scheme` or HF settings, propagated to your iframe.
|
|
45
|
-
|
|
46
|
-
What **you** write:
|
|
47
|
-
|
|
48
|
-
- `index.html` + `Dockerfile` (boilerplate, ~10 lines each).
|
|
49
|
-
- `src/dispatch.ts` (~20 lines, picks shell vs embed mode).
|
|
50
|
-
- `src/embed.{ts,tsx}` - **your app's actual code**. You receive a
|
|
51
|
-
live `ReachyMini` SDK handle and render whatever you want.
|
|
52
|
-
|
|
53
|
-
You can use **any framework** inside your `embed` entry: React,
|
|
54
|
-
Svelte, Vue, vanilla TS. The host runs outside your iframe and
|
|
55
|
-
doesn't care.
|
|
56
|
-
|
|
57
|
-
---
|
|
58
|
-
|
|
59
|
-
## 2. Quickstart: clone a reference app
|
|
60
|
-
|
|
61
|
-
Pick the reference closest to your needs and clone its `index.html`
|
|
62
|
-
+ `dispatch.ts` + `Dockerfile`:
|
|
63
|
-
|
|
64
|
-
| Reference app | Stack | Use it for |
|
|
65
|
-
|-------------------------------------|-----------------------------|---------------------------------------------|
|
|
66
|
-
| `reachy_mini_emotions/` | React 19 + MUI 7 + Vite | UI-rich app with rich components / theming |
|
|
67
|
-
| `reachy_mini_telepresence/` | React 19 + MUI 7 + Vite | App with camera / media streams |
|
|
68
|
-
| `reachy_mini_minimal_conversation/` | **Vanilla TS + Vite** | Smallest runtime, no framework |
|
|
69
|
-
|
|
70
|
-
```bash
|
|
71
|
-
# Example: start from the vanilla TS template
|
|
72
|
-
cp -r reachy_mini_minimal_conversation/ my_new_app/
|
|
73
|
-
cd my_new_app
|
|
74
|
-
# Edit package.json `name`, README frontmatter (`title`, `emoji`),
|
|
75
|
-
# and src/embed.ts to your app.
|
|
76
|
-
npm install
|
|
77
|
-
npm run dev
|
|
78
|
-
# → http://localhost:5173
|
|
79
|
-
```
|
|
80
|
-
|
|
81
|
-
---
|
|
82
|
-
|
|
83
|
-
## 3. The 4-file contract
|
|
84
|
-
|
|
85
|
-
These four files are the entire integration surface. Don't add
|
|
86
|
-
mandatory files outside this list - keep the apps interchangeable.
|
|
87
|
-
|
|
88
|
-
### 3.1 `index.html`
|
|
89
|
-
|
|
90
|
-
```html
|
|
91
|
-
<!doctype html>
|
|
92
|
-
<html lang="en">
|
|
93
|
-
<head>
|
|
94
|
-
<meta charset="UTF-8" />
|
|
95
|
-
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
96
|
-
<title>My App</title>
|
|
97
|
-
<link rel="icon" href="/icon.svg" type="image/svg+xml" />
|
|
98
|
-
|
|
99
|
-
<!-- Theme bootstrap: paints the right palette before CSS lands -->
|
|
100
|
-
<script>
|
|
101
|
-
(function () {
|
|
102
|
-
var params = new URLSearchParams(window.location.search);
|
|
103
|
-
var raw = params.get("theme");
|
|
104
|
-
var mode = (raw === "dark" || raw === "light")
|
|
105
|
-
? raw
|
|
106
|
-
: (window.matchMedia && window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light");
|
|
107
|
-
document.documentElement.setAttribute("data-theme", mode);
|
|
108
|
-
document.documentElement.style.backgroundColor = mode === "dark" ? "#101013" : "#fafafa";
|
|
109
|
-
})();
|
|
110
|
-
</script>
|
|
111
|
-
|
|
112
|
-
<!-- HF Spaces injects OAuth client ID at container start -->
|
|
113
|
-
<script>
|
|
114
|
-
(function () {
|
|
115
|
-
var clientId = "__OAUTH_CLIENT_ID__";
|
|
116
|
-
if (clientId && clientId.indexOf("__") !== 0) {
|
|
117
|
-
window.huggingface = { variables: { OAUTH_CLIENT_ID: clientId } };
|
|
118
|
-
}
|
|
119
|
-
})();
|
|
120
|
-
</script>
|
|
121
|
-
|
|
122
|
-
<!-- ReachyMini SDK, pinned to a MAJOR version (e.g. @1) for auto-patch
|
|
123
|
-
updates, or to a TAG / SHA via `cdn.jsdelivr.net/gh/...` if you
|
|
124
|
-
need bleeding-edge. The `+esm` suffix resolves the bare
|
|
125
|
-
@huggingface/hub dependency recursively. -->
|
|
126
|
-
<script type="module">
|
|
127
|
-
import { ReachyMini } from "https://cdn.jsdelivr.net/npm/@pollen-robotics/reachy-mini-sdk@1/+esm";
|
|
128
|
-
window.ReachyMini = ReachyMini;
|
|
129
|
-
window.dispatchEvent(new Event("reachymini:ready"));
|
|
130
|
-
</script>
|
|
131
|
-
</head>
|
|
132
|
-
<body>
|
|
133
|
-
<div id="root"></div>
|
|
134
|
-
<script type="module" src="/src/dispatch.ts"></script>
|
|
135
|
-
</body>
|
|
136
|
-
</html>
|
|
137
|
-
```
|
|
138
|
-
|
|
139
|
-
### 3.2 `src/dispatch.ts`
|
|
140
|
-
|
|
141
|
-
```ts
|
|
142
|
-
const params = new URLSearchParams(window.location.search);
|
|
143
|
-
|
|
144
|
-
if (params.get('embedded') === '1') {
|
|
145
|
-
void import('./embed');
|
|
146
|
-
} else {
|
|
147
|
-
void import('@pollen-robotics/reachy-mini-sdk/host/auto').then(({ mountHost }) => {
|
|
148
|
-
mountHost({
|
|
149
|
-
appName: 'My App',
|
|
150
|
-
appIconUrl: '/icon.svg',
|
|
151
|
-
enableMicrophone: false,
|
|
152
|
-
});
|
|
153
|
-
});
|
|
154
|
-
}
|
|
155
|
-
```
|
|
156
|
-
|
|
157
|
-
### 3.3 `src/embed.ts` (vanilla example)
|
|
158
|
-
|
|
159
|
-
```ts
|
|
160
|
-
import { connectToHost } from '@pollen-robotics/reachy-mini-sdk/host/embed';
|
|
161
|
-
|
|
162
|
-
interface MyConfig {
|
|
163
|
-
startingEmotion?: string;
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
async function main() {
|
|
167
|
-
const handle = await connectToHost<MyConfig>();
|
|
168
|
-
const { reachy, theme, config, onLeave } = handle;
|
|
169
|
-
|
|
170
|
-
document.body.innerHTML = '<h1>Connected!</h1>';
|
|
171
|
-
|
|
172
|
-
reachy.setHeadRpyDeg(0, 10, 0);
|
|
173
|
-
|
|
174
|
-
onLeave(async () => {
|
|
175
|
-
document.body.innerHTML = '<p>Bye!</p>';
|
|
176
|
-
});
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
void main().catch((err) => {
|
|
180
|
-
console.error('[my-app] boot failed', err);
|
|
181
|
-
window.parent.postMessage(
|
|
182
|
-
{ source: 'reachy-mini', type: 'embed:error', version: 1,
|
|
183
|
-
message: String(err), fatal: true },
|
|
184
|
-
window.location.origin,
|
|
185
|
-
);
|
|
186
|
-
});
|
|
187
|
-
```
|
|
188
|
-
|
|
189
|
-
### 3.4 `Dockerfile`
|
|
190
|
-
|
|
191
|
-
Standard nginx + node build. Copy from `reachy_mini_emotions/Dockerfile`
|
|
192
|
-
verbatim; the only thing to customise is the app name in comments.
|
|
193
|
-
|
|
194
|
-
---
|
|
195
|
-
|
|
196
|
-
## 4. `mountHost()` API
|
|
197
|
-
|
|
198
|
-
Called once from `dispatch.ts` when the URL is **not** in embed mode.
|
|
199
|
-
Renders the shell into `#root`. The shell's visual theme (MUI
|
|
200
|
-
light/dark) is bundled and not overridable - the host owns its
|
|
201
|
-
look, apps own theirs inside the iframe.
|
|
202
|
-
|
|
203
|
-
```ts
|
|
204
|
-
import { mountHost } from '@pollen-robotics/reachy-mini-sdk/host/auto';
|
|
205
|
-
|
|
206
|
-
mountHost({
|
|
207
|
-
appName: 'My App', // REQUIRED: passed to the SDK + shown in top bar
|
|
208
|
-
appIconUrl: '/icon.svg', // optional: top-bar logo (see §6)
|
|
209
|
-
appEmoji: '🤖', // optional: fallback when no icon.svg
|
|
210
|
-
enableMicrophone: false, // false unless you need WebRTC audio in
|
|
211
|
-
clientId: undefined, // optional: HF OAuth client ID; defaults to window.huggingface.variables.OAUTH_CLIENT_ID
|
|
212
|
-
devToken: undefined, // optional: { token, userName } - dev shortcut, see §9
|
|
213
|
-
target: undefined, // optional: HTMLElement | string CSS selector; default '#root'
|
|
214
|
-
});
|
|
215
|
-
```
|
|
216
|
-
|
|
217
|
-
**Required**: `appName`. Everything else has sensible defaults.
|
|
218
|
-
|
|
219
|
-
**Return**: `{ dispose(): void }` - call to unmount cleanly. You
|
|
220
|
-
usually never need this; the page lifecycle handles it.
|
|
221
|
-
|
|
222
|
-
---
|
|
223
|
-
|
|
224
|
-
## 5. `connectToHost()` API
|
|
225
|
-
|
|
226
|
-
Called once from `embed.{ts,tsx}` to get a live SDK handle.
|
|
227
|
-
|
|
228
|
-
```ts
|
|
229
|
-
import { connectToHost } from '@pollen-robotics/reachy-mini-sdk/host/embed';
|
|
230
|
-
|
|
231
|
-
interface MyConfig { /* whatever your app accepts */ }
|
|
232
|
-
|
|
233
|
-
const handle = await connectToHost<MyConfig>();
|
|
234
|
-
```
|
|
235
|
-
|
|
236
|
-
Awaiting `connectToHost()` blocks until:
|
|
237
|
-
|
|
238
|
-
1. The URL hash creds are parsed and wiped.
|
|
239
|
-
2. The SDK script loaded (`window.ReachyMini` ready).
|
|
240
|
-
3. The host posted `host:init` (Mode A only; Mode B times out
|
|
241
|
-
after 8 s and proceeds from hash alone).
|
|
242
|
-
4. The SDK connected, started a session, and woke the robot.
|
|
243
|
-
|
|
244
|
-
The resolved `handle` exposes:
|
|
245
|
-
|
|
246
|
-
```ts
|
|
247
|
-
interface ConnectedHandle<TConfig> {
|
|
248
|
-
// Live state at boot
|
|
249
|
-
reachy: ReachyMiniInstance; // SDK instance, session live, robot awake
|
|
250
|
-
theme: 'dark' | 'light';
|
|
251
|
-
config: TConfig | null;
|
|
252
|
-
appName: string;
|
|
253
|
-
hostName: string;
|
|
254
|
-
userName: string | null;
|
|
255
|
-
|
|
256
|
-
// Subscribe to live updates from the host (returns an unsub fn)
|
|
257
|
-
onLeave(cb: () => void | Promise<void>): () => void;
|
|
258
|
-
onThemeChange(cb: (theme: 'dark' | 'light') => void): () => void;
|
|
259
|
-
onConfigChange(cb: (config: TConfig | null) => void): () => void;
|
|
260
|
-
|
|
261
|
-
// Push state / requests back to the host
|
|
262
|
-
setAppState(s: { phase, connectingStep?, message? }): void;
|
|
263
|
-
requestLeave(): void; // ask host to end the session
|
|
264
|
-
reportError(msg: string, opts?: { fatal?, detail? }): void;
|
|
265
|
-
}
|
|
266
|
-
```
|
|
267
|
-
|
|
268
|
-
The API is intentionally minimal. If you need a custom channel
|
|
269
|
-
between host and embed, file a feature request - we'll add it as
|
|
270
|
-
a typed message rather than expose a free-form sink.
|
|
271
|
-
|
|
272
|
-
### Typing your config
|
|
273
|
-
|
|
274
|
-
`connectToHost<T>()` types the `config` field; runtime validation
|
|
275
|
-
is **your job**. An attacker controlling the URL can shape config
|
|
276
|
-
freely - cast is not enough.
|
|
277
|
-
|
|
278
|
-
```ts
|
|
279
|
-
const handle = await connectToHost<MyConfig>();
|
|
280
|
-
const config = isValidConfig(handle.config) ? handle.config : null;
|
|
281
|
-
```
|
|
282
|
-
|
|
283
|
-
---
|
|
284
|
-
|
|
285
|
-
## 6. Visual identity: icon, name, emoji
|
|
286
|
-
|
|
287
|
-
> **App identity is your HF Space ID** (`owner/space`). An app
|
|
288
|
-
> published at `huggingface.co/spaces/your-name/cool-thing` has
|
|
289
|
-
> identity `your-name/cool-thing` everywhere downstream (mobile
|
|
290
|
-
> catalog, "last opened", official badge). Apps in the
|
|
291
|
-
> `pollen-robotics/*` namespace are automatically tagged as
|
|
292
|
-
> official in the catalog; no extra config.
|
|
293
|
-
|
|
294
|
-
Your app's identity surfaces in **three independent places**, all
|
|
295
|
-
fed from the same sources but each by its own resolution path:
|
|
296
|
-
|
|
297
|
-
| Surface | What it shows | How it gets there |
|
|
298
|
-
|-------------------------------|----------------------------------------|----------------------------------------------------------------------------------------------------|
|
|
299
|
-
| Host top bar (in your iframe) | App logo + app name | You pass `appName` + `appIconUrl` + `appEmoji` to `mountHost()` in `dispatch.ts` |
|
|
300
|
-
| Hugging Face mobile catalog | App tile (icon + title + description) | The mobile reads the catalog API; the API reads HF Spaces' frontmatter + probes `public/icon.svg` |
|
|
301
|
-
| Browser tab / OS | Favicon | Your `index.html` `<link rel="icon" href="/icon.svg">` |
|
|
302
|
-
|
|
303
|
-
The host shell itself **does not discover or list other apps**.
|
|
304
|
-
It renders only the app it lives in. The catalog is owned by the
|
|
305
|
-
mobile and the website API; see §11 FAQ for the API spec if you
|
|
306
|
-
need to validate your app shows up.
|
|
307
|
-
|
|
308
|
-
### Single source of truth: one `icon.svg`, one Space frontmatter
|
|
309
|
-
|
|
310
|
-
You ship **one** SVG file and **one** README frontmatter; both
|
|
311
|
-
the host and the mobile catalog read from them:
|
|
312
|
-
|
|
313
|
-
1. **`public/icon.svg`** in your repo. Vite copies it verbatim to
|
|
314
|
-
`dist/icon.svg`, where nginx serves it at the root URL of your
|
|
315
|
-
Space. Reference it from `index.html`:
|
|
316
|
-
|
|
317
|
-
```html
|
|
318
|
-
<link rel="icon" href="/icon.svg" type="image/svg+xml" />
|
|
319
|
-
```
|
|
320
|
-
|
|
321
|
-
Pass it to `mountHost()` so the top bar renders it without a
|
|
322
|
-
probe:
|
|
323
|
-
|
|
324
|
-
```ts
|
|
325
|
-
mountHost({ appName: 'My App', appIconUrl: '/icon.svg' });
|
|
326
|
-
```
|
|
327
|
-
|
|
328
|
-
The catalog API also picks it up by listing the Space's
|
|
329
|
-
files (`siblings`) and matching `public/icon.svg` (or
|
|
330
|
-
`public/icon.png`), no live probe required.
|
|
331
|
-
|
|
332
|
-
2. **HF Space frontmatter** in your `README.md`:
|
|
333
|
-
|
|
334
|
-
```yaml
|
|
335
|
-
---
|
|
336
|
-
title: My App
|
|
337
|
-
emoji: 🤖
|
|
338
|
-
colorFrom: yellow
|
|
339
|
-
colorTo: red
|
|
340
|
-
sdk: docker
|
|
341
|
-
app_port: 7860
|
|
342
|
-
pinned: false
|
|
343
|
-
hf_oauth: true
|
|
344
|
-
short_description: One-line description shown in the catalog.
|
|
345
|
-
tags:
|
|
346
|
-
- reachy_mini
|
|
347
|
-
- reachy_mini_js_app
|
|
348
|
-
---
|
|
349
|
-
```
|
|
350
|
-
|
|
351
|
-
- `title` is the app name in the mobile catalog (the in-iframe
|
|
352
|
-
top bar uses `appName` from `mountHost()` instead).
|
|
353
|
-
- `emoji` is the fallback logo when no `icon.svg` is shipped.
|
|
354
|
-
Pass the same value to `mountHost({ appEmoji: '🤖' })` so the
|
|
355
|
-
top bar's fallback matches.
|
|
356
|
-
- `short_description` shows under the app tile in the catalog.
|
|
357
|
-
- The **`reachy_mini_js_app` tag is mandatory** to appear in
|
|
358
|
-
the mobile catalog. The catalog API filters on this exact
|
|
359
|
-
string. Don't remove it.
|
|
360
|
-
- `hf_oauth: true` makes HF auto-provision an OAuth client and
|
|
361
|
-
inject the ID at container start.
|
|
362
|
-
|
|
363
|
-
### Icon design recommendations
|
|
364
|
-
|
|
365
|
-
Your icon renders at three different sizes; design for all three:
|
|
366
|
-
|
|
367
|
-
- **Host top bar inside the iframe**: ~24x24 px square.
|
|
368
|
-
- **Mobile catalog tile**: ~64x64 px square card.
|
|
369
|
-
- **Browser tab favicon**: 16x16 px.
|
|
370
|
-
|
|
371
|
-
Practical guidance:
|
|
372
|
-
|
|
373
|
-
- Use a **square viewBox** (e.g. `viewBox="0 0 24 24"`) so the
|
|
374
|
-
three target sizes all crop identically.
|
|
375
|
-
- Keep the icon **readable at 16 px**: thick strokes, simple
|
|
376
|
-
silhouette, max 2-3 distinct shapes.
|
|
377
|
-
- Inline all colours; **don't reference external CSS**, the icon
|
|
378
|
-
is served standalone.
|
|
379
|
-
- Respect dark and light backgrounds: an icon that vanishes on
|
|
380
|
-
light should provide a `<style>` tag with `@media (prefers-color-scheme)`
|
|
381
|
-
rules or, simpler, use a neutral mid-tone palette.
|
|
382
|
-
- **Optimise the SVG**: target ~30 KB or less. Tools: `svgo`,
|
|
383
|
-
Figma's "Export SVG → optimise".
|
|
384
|
-
|
|
385
|
-
### PNG fallback
|
|
386
|
-
|
|
387
|
-
If you can't ship SVG (heavy raster art, exported portrait, ...),
|
|
388
|
-
the catalog API also accepts `public/icon.png`. SVG wins when both
|
|
389
|
-
exist. The host top bar only renders the SVG variant - if your
|
|
390
|
-
`mountHost({ appIconUrl })` points at a PNG, it works, but you
|
|
391
|
-
lose the crisp upscale on hi-DPI screens.
|
|
392
|
-
|
|
393
|
-
---
|
|
394
|
-
|
|
395
|
-
## 7. Receiving an external config
|
|
396
|
-
|
|
397
|
-
The host accepts a base64-encoded JSON `config` from two sources:
|
|
398
|
-
|
|
399
|
-
1. **URL parameter**: `https://<space>.hf.space/?config=eyJlbW90aW9uIjoiam95In0=`
|
|
400
|
-
(decoded once, passed verbatim).
|
|
401
|
-
2. **Mobile handoff**: the mobile app embeds your Space with
|
|
402
|
-
`?embedded=1#creds=<base64-bundle-including-config>`.
|
|
403
|
-
|
|
404
|
-
Your app receives `config` typed as `unknown`; cast and validate.
|
|
405
|
-
|
|
406
|
-
```ts
|
|
407
|
-
interface MyConfig { startingEmotion?: string; }
|
|
408
|
-
|
|
409
|
-
function isMyConfig(v: unknown): v is MyConfig {
|
|
410
|
-
return v != null && typeof v === 'object' && (
|
|
411
|
-
(v as MyConfig).startingEmotion === undefined ||
|
|
412
|
-
typeof (v as MyConfig).startingEmotion === 'string'
|
|
413
|
-
);
|
|
414
|
-
}
|
|
415
|
-
|
|
416
|
-
const handle = await connectToHost<MyConfig>();
|
|
417
|
-
const initial: MyConfig = isMyConfig(handle.config) ? handle.config : {};
|
|
418
|
-
|
|
419
|
-
handle.onConfigChange((next) => {
|
|
420
|
-
if (isMyConfig(next)) /* react to it */;
|
|
421
|
-
});
|
|
422
|
-
```
|
|
423
|
-
|
|
424
|
-
If your app's UI state changes in a way the mobile would want to
|
|
425
|
-
remember (e.g. user picked a different emotion), persist it in
|
|
426
|
-
your app's storage. The host does **not** propagate state
|
|
427
|
-
upstream - apps don't push config to the host in v1.
|
|
428
|
-
|
|
429
|
-
---
|
|
430
|
-
|
|
431
|
-
## 8. Cleaning up on leave
|
|
432
|
-
|
|
433
|
-
The host fires a tear-down sequence in three scenarios:
|
|
434
|
-
|
|
435
|
-
- User clicks "End session" / "Back to apps" in the top bar.
|
|
436
|
-
- Your app calls `handle.requestLeave()`.
|
|
437
|
-
- The page is unloaded (`pagehide`, e.g. user closes the tab).
|
|
438
|
-
|
|
439
|
-
In all three cases your `onLeave` callbacks fire. You have **~1.5-2 s**
|
|
440
|
-
before the host force-unmounts the iframe; use that to:
|
|
441
|
-
|
|
442
|
-
```ts
|
|
443
|
-
handle.onLeave(async () => {
|
|
444
|
-
player.cancel(); // stop streaming motion frames
|
|
445
|
-
audioCtx?.close(); // release audio
|
|
446
|
-
ws?.close(); // close any side channels
|
|
447
|
-
await flushTelemetry(); // your async hooks
|
|
448
|
-
});
|
|
449
|
-
```
|
|
450
|
-
|
|
451
|
-
You do **not** need to call `reachy.stopSession()` yourself - the
|
|
452
|
-
host does. You also don't need to navigate away; the iframe is
|
|
453
|
-
unmounted by the host.
|
|
454
|
-
|
|
455
|
-
---
|
|
456
|
-
|
|
457
|
-
## 9. Local dev
|
|
458
|
-
|
|
459
|
-
You have two options, picked by the `devToken` and `clientId`
|
|
460
|
-
props passed to `mountHost()`. Reference apps support both via
|
|
461
|
-
`.env.local`.
|
|
462
|
-
|
|
463
|
-
### Option A: personal access token (no OAuth)
|
|
464
|
-
|
|
465
|
-
Fastest for local dev. Skips the OAuth redirect entirely.
|
|
466
|
-
|
|
467
|
-
1. Get a token at <https://huggingface.co/settings/tokens> (read
|
|
468
|
-
scope is enough).
|
|
469
|
-
2. Create `.env.local`:
|
|
470
|
-
|
|
471
|
-
```
|
|
472
|
-
VITE_HF_TOKEN=hf_xxx
|
|
473
|
-
VITE_HF_USERNAME=your-handle
|
|
474
|
-
```
|
|
475
|
-
|
|
476
|
-
3. In `dispatch.ts`, forward both to `mountHost`:
|
|
477
|
-
|
|
478
|
-
```ts
|
|
479
|
-
mountHost({
|
|
480
|
-
appName: 'My App',
|
|
481
|
-
devToken: import.meta.env.VITE_HF_TOKEN && import.meta.env.VITE_HF_USERNAME
|
|
482
|
-
? { token: import.meta.env.VITE_HF_TOKEN, userName: import.meta.env.VITE_HF_USERNAME }
|
|
483
|
-
: undefined,
|
|
484
|
-
});
|
|
485
|
-
```
|
|
486
|
-
|
|
487
|
-
4. `npm run dev` → you're signed in on page load.
|
|
488
|
-
|
|
489
|
-
`.env.local` must be gitignored. **Never commit the token.**
|
|
490
|
-
|
|
491
|
-
### Option B: real OAuth client ID
|
|
492
|
-
|
|
493
|
-
Use this when you're touching the OAuth / logout paths.
|
|
494
|
-
|
|
495
|
-
1. Go to <https://huggingface.co/settings/applications/new>.
|
|
496
|
-
2. Homepage URL: `http://localhost:5173` · Redirect URIs:
|
|
497
|
-
`http://localhost:5173` · Scopes: at least `openid`, `profile`.
|
|
498
|
-
3. Copy the client ID into `.env.local`:
|
|
499
|
-
|
|
500
|
-
```
|
|
501
|
-
VITE_HF_OAUTH_CLIENT_ID=...
|
|
502
|
-
```
|
|
503
|
-
|
|
504
|
-
4. Forward to `mountHost`:
|
|
505
|
-
|
|
506
|
-
```ts
|
|
507
|
-
mountHost({
|
|
508
|
-
appName: 'My App',
|
|
509
|
-
clientId: import.meta.env.VITE_HF_OAUTH_CLIENT_ID,
|
|
510
|
-
});
|
|
511
|
-
```
|
|
512
|
-
|
|
513
|
-
---
|
|
514
|
-
|
|
515
|
-
## 10. Deploying to Hugging Face Spaces
|
|
516
|
-
|
|
517
|
-
The reference apps' `Dockerfile` is ready to copy. Steps:
|
|
518
|
-
|
|
519
|
-
```bash
|
|
520
|
-
# 1. Init the Space repo
|
|
521
|
-
huggingface-cli repo create my-app --type space --space_sdk docker
|
|
522
|
-
|
|
523
|
-
# 2. Push
|
|
524
|
-
git remote add space git@hf.co:spaces/<your-username>/my-app
|
|
525
|
-
git push space main
|
|
526
|
-
```
|
|
527
|
-
|
|
528
|
-
HF Spaces will:
|
|
529
|
-
|
|
530
|
-
- Build your `Dockerfile` (the multi-stage `node:20-alpine` → `nginx:1.27-alpine` setup).
|
|
531
|
-
- At container start, run `docker-entrypoint.d/10-inject-hf-vars.sh`
|
|
532
|
-
to patch `index.html` with the real OAuth client ID, space host,
|
|
533
|
-
and space ID.
|
|
534
|
-
- Serve nginx on `app_port` (7860).
|
|
535
|
-
|
|
536
|
-
### Required Space frontmatter
|
|
537
|
-
|
|
538
|
-
```yaml
|
|
539
|
-
sdk: docker
|
|
540
|
-
app_port: 7860
|
|
541
|
-
hf_oauth: true
|
|
542
|
-
tags:
|
|
543
|
-
- reachy_mini
|
|
544
|
-
- reachy_mini_js_app # critical: mobile app discovery
|
|
545
|
-
```
|
|
546
|
-
|
|
547
|
-
### Cache busting
|
|
548
|
-
|
|
549
|
-
If after a push your Space still serves the old bundle, push an
|
|
550
|
-
empty commit:
|
|
551
|
-
|
|
552
|
-
```bash
|
|
553
|
-
git commit --allow-empty -m "chore: bust HF Spaces cache"
|
|
554
|
-
git push space main
|
|
555
|
-
```
|
|
556
|
-
|
|
557
|
-
---
|
|
558
|
-
|
|
559
|
-
## 11. FAQ and common pitfalls
|
|
560
|
-
|
|
561
|
-
### "I see a `Robot is busy` error even though no one is using the robot"
|
|
562
|
-
|
|
563
|
-
The host's SDK and the embed's SDK both claim a peer at the
|
|
564
|
-
central. The host **must** disconnect when the embed boots; if it
|
|
565
|
-
doesn't, the central sees two peers with the same `appName` and
|
|
566
|
-
rejects the embed.
|
|
567
|
-
|
|
568
|
-
This is handled automatically by `@pollen-robotics/reachy-mini-sdk/host` (§8.1 of the
|
|
569
|
-
SPEC). If you see this in dev, you likely have **two tabs** open
|
|
570
|
-
on the same Space - that's expected behaviour.
|
|
571
|
-
|
|
572
|
-
### "My app loads React + MUI even though I wrote vanilla TS"
|
|
573
|
-
|
|
574
|
-
The **host shell** is React + MUI. It runs **only outside your
|
|
575
|
-
iframe** (sign-in screen, picker, top bar). Once your app is live,
|
|
576
|
-
the host's React tree is idle.
|
|
577
|
-
|
|
578
|
-
Your iframe content is whatever you wrote. Vanilla TS apps stay
|
|
579
|
-
slim inside the iframe.
|
|
580
|
-
|
|
581
|
-
### "Vite warns about React being installed in two places"
|
|
582
|
-
|
|
583
|
-
You're using the legacy `file:./vendor/reachy-mini-host` dep
|
|
584
|
-
pattern (now unsupported — the host ships from npm as part of
|
|
585
|
-
`@pollen-robotics/reachy-mini-sdk`). Migrate to the npm dep and,
|
|
586
|
-
if you still see the warning, add to your `vite.config.ts`:
|
|
587
|
-
|
|
588
|
-
```ts
|
|
589
|
-
export default defineConfig({
|
|
590
|
-
resolve: {
|
|
591
|
-
dedupe: ['react', 'react-dom', 'react/jsx-runtime',
|
|
592
|
-
'@emotion/react', '@emotion/styled',
|
|
593
|
-
'@mui/material', '@mui/icons-material'],
|
|
594
|
-
},
|
|
595
|
-
optimizeDeps: {
|
|
596
|
-
include: ['@pollen-robotics/reachy-mini-sdk',
|
|
597
|
-
'@pollen-robotics/reachy-mini-sdk/host',
|
|
598
|
-
'@pollen-robotics/reachy-mini-sdk/host/auto',
|
|
599
|
-
'@pollen-robotics/reachy-mini-sdk/host/embed'],
|
|
600
|
-
},
|
|
601
|
-
});
|
|
602
|
-
```
|
|
603
|
-
|
|
604
|
-
### "I want a different sign-in flow"
|
|
605
|
-
|
|
606
|
-
Not supported in v1. The host owns OAuth. If you need a custom
|
|
607
|
-
flow, the standalone shell isn't for you - publish your Space
|
|
608
|
-
with the host disabled (just don't call `mountHost()`) and roll
|
|
609
|
-
your own.
|
|
610
|
-
|
|
611
|
-
### "I want a different theme than the bundled MUI one"
|
|
612
|
-
|
|
613
|
-
The host shell's look is fixed (light + dark MUI bundle). Apps
|
|
614
|
-
own their own theme **inside the iframe** - use the
|
|
615
|
-
`handle.theme` value as your mode signal and wrap your app in
|
|
616
|
-
whatever ThemeProvider you want.
|
|
617
|
-
|
|
618
|
-
```ts
|
|
619
|
-
const handle = await connectToHost();
|
|
620
|
-
// Mirror `handle.theme` ('dark' | 'light') in your own
|
|
621
|
-
// ThemeProvider. The host pushes updates via onThemeChange().
|
|
622
|
-
```
|
|
623
|
-
|
|
624
|
-
### "The icon doesn't show up in the top bar"
|
|
625
|
-
|
|
626
|
-
Check the three sources in priority order (§6):
|
|
627
|
-
|
|
628
|
-
1. Is `/icon.svg` reachable at the deployed URL? Open
|
|
629
|
-
`https://<space>.hf.space/icon.svg` directly.
|
|
630
|
-
2. Is the file's MIME type `image/svg+xml`? The host's probe
|
|
631
|
-
checks the response's `content-type`.
|
|
632
|
-
3. Did you pass `appIconUrl: '/icon.svg'` to `mountHost()`?
|
|
633
|
-
|
|
634
|
-
If 1 + 2 + 3 are correct and it still fails, file a bug.
|
|
635
|
-
|
|
636
|
-
### "How do I test the mobile-handoff mode locally?"
|
|
637
|
-
|
|
638
|
-
Hit your dev server at:
|
|
639
|
-
|
|
640
|
-
```
|
|
641
|
-
http://localhost:5173/?embedded=1#creds=<base64({"hfToken":"hf_xxx","userName":"you","robotPeerId":"abc","signalingUrl":"https://...","theme":"dark","config":null,"hostName":"Reachy Mini","appName":"My App"})>
|
|
642
|
-
```
|
|
643
|
-
|
|
644
|
-
The dispatcher will skip the shell and go straight to your embed.
|
|
645
|
-
Useful for testing the embed path without spinning up the mobile
|
|
646
|
-
app.
|