Cinématique d'Introduction Hermes
Hermes Desktop livre déjà un film d'introduction de 22 secondes — sept temps, un viewport cube en direct, des agents parallèles, la marque Nous — avec un son synthétisé. Il existe, mais presque personne ne le voit deux fois : l'éligibilité est guestOnboardingEnabled && !seen.
Cette compétence fait jouer le film à chaque lancement et vous permet de superposer votre propre piste vocale sur les signaux intégrés.
Quand l'utiliser
- « Faire en sorte que l'animation de démarrage d'Hermes se joue à chaque fois. »
- « Mettre mon audio sur l'animation de démarrage — superposé, sans remplacer les effets sonores. »
- Toute modification dans
apps/desktop/src/components/intro-reveal/.
Ce qu'est déjà le film
| Élément | Chemin |
|---|---|
| Porte (quand il joue) | apps/desktop/src/components/intro-reveal/index.tsx |
| Éligibilité + store de phase | apps/desktop/src/store/intro-reveal.ts |
| Superposition boot | src/components/intro-reveal/intro-root.tsx |
| Horloge, score, canvas | use-intro-clock.ts, timeline.ts, viewport-cube.ts |
| Signaux synthétisés | sound.ts |
| Fenêtre native | electron/intro-reveal-window.ts, electron/main.ts |
Calcul des temps : INTRO_TOTAL_MS = 17 600 est le temps de score ; le temps réel est × INTRO_PACE (1.25) = 22 000 ms, plus INTRO_EXIT_MS 900 ms → 23 125 ms au total.
Rejouer à chaque lancement — trois modifications, toutes requises
src/store/intro-reveal.ts—isIntroRevealEnabled()ne doit plus exiger l'onboarding invité ;shouldPlayFirstRunIntro()retourne!playedThisSessionen dehors de l'onboarding. Un indicateur moduleplayedThisSessionest obligatoire : l'effet de la porte se relance à chaque fois que la phase revient à'hidden', ce qui est exactement ce que fait la fin du film — sans l'indicateur, il boucle indéfiniment.src/components/intro-reveal/intro-root.tsx— la fenêtre de superposition vérifieisOnboardingEnabled()et retournerait avant de peindre. Remplacez-la par le même prédicat que le moteur de rendu principal.electron/main.ts—introRevealControllerest construit avecenabled: GUEST_ONBOARDING, donc le processus principal refuseopen. Mettez-le àtrue; la vivacité est décidée dans le moteur de rendu.
beginOnboardingFlow() / queueGuideAfterIntro() / takeGuideShape() de l'onboarding se protègent tous eux-mêmes avec isOnboardingEnabled(), donc ils ne font rien lorsque l'onboarding est désactivé — aucune modification supplémentaire nécessaire.
Interrupteur de désactivation : localStorage['hermes-intro-reveal-always-v1'] = '0', ou lancez avec HERMES_SKIP_INTRO=1 / --skip-intro.
Superposer une piste audio
Placez un fichier dans apps/desktop/public/intro-voiceover.mp3 (vite copie public/ dans dist/ à l'identique), puis dans sound.ts :
export function startVoiceover(cutAtMs: number): { stop: () => void }
— créez un élément Audio, réglez volume, démarrez-le, et setTimeout(stop, cutAtMs - 300) pour que les 300 ms finales diminuent progressivement au lieu de claquer. Appelez-le à côté de startPad() dans use-intro-clock.ts et arrêtez-le dans le nettoyage de l'effet.
Utilisez un élément <audio>, jamais fetch() + decodeAudioData(). Le moteur de rendu packagé tourne sur file://, où fetch et decodeAudioData sont bloqués par CORS ; les éléments média chargent correctement les actifs file://.
Les deux pistes sont indépendantes par construction — l'audio des éléments et les signaux WebAudio se mélangent au niveau du système d'exploitation. Rien ne diminue rien. Câblez $hapticsMuted aux deux, et abonnez-vous plutôt que de le lire une seule fois, sinon le basculement de sourdine se désynchronise.
Point de coupure :
const cutMs = Math.round((INTRO_TOTAL_MS + INTRO_EXIT_MS) * INTRO_PACE) // 23 125
Livrer dans une application packagée
Les modifications dans le code source ne font rien tant que l'asar n'est pas reconditionné. Deux pièges ici, qui valent la peine d'être vérifiés :
Trouvez l'application que l'utilisateur lance réellement — ne supposez jamais /Applications.
ps aux | grep '[H]ermes.app/Contents/MacOS/Hermes'
defaults read com.apple.dock persistent-apps | grep -o '/[^"]*Hermes\.app'
Les mises à jour d'Hermes atterrissent dans ~/.hermes/hermes-agent*/apps/desktop/release/mac-arm64/Hermes.app et le Dock pointe souvent là, tandis que /Applications/Hermes.app peut être une copie obsolète 0.0.0. Confirmez avec CFBundleShortVersionString (0.17.x est réel, 0.0.0 ne l'est pas).
Rafraîchissez aussi app.asar.unpacked/dist/. Les builds packagés listent dist/** dans asarUnpack, et resolveRendererIndex() préfère cette copie — reconditionner le asar seul et l'arborescence dépaquetée obsolète l'éclipse.
npm install --workspace apps/desktop --ignore-scripts \
--engine-strict=false --min-release-age=0
cd apps/desktop && node ../../node_modules/vite/bin/vite.js build
node scripts/bundle-electron-main.mjs # les modifications du processus principal nécessitent aussi cette étape
asar extract "$RES/app.asar" "$WORK"
rm -rf "$WORK/dist" && cp -R apps/desktop/dist "$WORK/dist"
asar pack "$WORK" "$WORK/app.asar"
mv -f "$WORK/app.asar" "$RES/app.asar"
cp -R apps/desktop/dist/. "$RES/app.asar.unpacked/dist/"
codesign --force --deep -s - "$APP" # ad-hoc ; Hermes est livré signé ad-hoc
--ignore-scripts est important sur les réseaux lents : le téléchargement du binaire Electron n'est pas nécessaire pour construire ou reconditionner, seulement pour exécuter le développement.
Vérifier sans Electron
Rendre le film dans un navigateur classique — sans packaging, sans téléchargement de binaire :
cd apps/desktop && node ../../node_modules/vite/bin/vite.js --host 127.0.0.1 --port 5174
page.add_init_script("""
window.hermesDesktop = {
guestOnboardingEnabled: true, skipIntro: false,
introReveal: { ready(){}, skip(){},
open(){ return Promise.resolve() }, close(){ return Promise.resolve() },
onSkip(){ return () => {} }, onClosed(){ return () => {} } }
};
""")
page.goto("http://127.0.0.1:5174/?win=intro")
page.evaluate("import('/src/components/intro-reveal/intro-root.tsx').then(m => m.mountIntroReveal())")
L'appel de montage de la route lui-même ne fait rien sans le pont ; l'import manuel après le bouchage est ce qui peint réellement. Pour vérifier la piste audio, bouchonnez window.Audio et enregistrez les instances — assurez-vous que currentTime avance avec le film et que paused === true, volume === 0 au point de coupure.
Tests contractuels qui valent la peine d'être conservés dans src/store/intro-reveal.test.ts :
- joue lors d'un lancement normal même lorsque
hermes-intro-reveal-seen-v1est défini - joue exactement une fois —
finishIntroReveal()ne doit pas le réarmer - respecte la clé de désactivation et le drapeau de lancement
skipIntro - laisse les sémantiques de première exécution de l'onboarding intactes
Pièges
- Supprimer
playedThisSessiondonne une boucle de relecture infinie, pas un lancement sans bug. INTRO_TOTAL_MSest le temps de score. OublierINTRO_PACEcoupe la voix off 4,4 s trop tôt.- Le drapeau
enableddu processus principal et la garde de la superposition sont des portes séparées ; en manquer une ne rend rien, sans message d'erreur. - Quatre couches indépendantes maintiennent l'écran de retour (fermeture normale, Échap + 1,2 s de secours, deadman local, chien de garde du processus principal 34 s). N'en retirez pas une en modifiant les temps.
hermes updateremplace l'application par la build officielle. Le correctif persiste dans git ; relancez l'installateur après la mise à jour.