Appearance
Pièges spécifiques aux applis
Pièges transverses (V1→V2, async, noms de services, arguments chat) : voir
common/PITFALLS.md. Ci-dessous, ce qui est propre au runtime appli.
Cycle de vie
kapp.finish()est explicite. Une appli ne se termine pas quandscript.jsfinit de s'exécuter (contrairement au widget). Si tu oubliesfinish(), l'appli reste ouverte et les rewards accumulées ne sont jamais envoyées (sendRewards()est appelé parfinish()).- Pas de
window.executeAppFunction: ton code tourne dansscript.jsaprès l'eventAppliLoaded(généralement une IIFE(async () => { ... })()). kapp.display()affiche la fenêtre ; pour une applibacktasksans UI, tu n'en as pas besoin.- Nettoyage avant sortie :
kapp.onBeforeFinish(fn)/kapp.onBeforeAbort(fn)/kapp.onBeforeExit(status, fn)(handlers sync ou async, tous attendus).
Stockage : ne pas confondre les trois
kapp.preferences= config du streamer (définie viapreferences.config.js, éditée dans le dashboard). Lecture viaget(path)/ globalprefs.prefsn'est pas réactif : relis après unset.kapp.dataStorage= état global de l'appli (un seul jeu de données par app+channel).set(data)écrase tout (pas de merge auto) → relis avecget()et fusionne toi-même.kapp.userDataStorage/user.getAppData()/user.setAppData()= état par viewer. Lève une erreur (AppMethodError) hors contexte appli.kapp.logsStorage= historique empilé (append-only viaadd()), pour scores/événements ; pas pour de l'état mutable.
Préférences
preferences.config.jsest compilé enconfig/preferences.config.jsonpar le HUB (transpile). N'édite pas le.jsonà la main, il sera écrasé.- Les chemins de
get()suivent l'arbre des groupes :kapp.preferences.get('global.example.exempleItemTypeString'). get(path)renvoieundefinedsi le chemin n'existe pas — garde tes valeurs par défaut.- Le
defaultd'un itemmediaFilene peut référencer QUE la bibliothèque publique Kappapps :default: { library: '<slug>' }. Une clémedia(id de fichier channel) en default = CRIT à la validation ; un slug inexistant = CRIT ; un asset dépublié = WARN. La valeur hydratée ({file, volume, position}) se joue telle quelle aveckapp.medias.playMedia(pref).
Rewards
kapp.rewards.winner(user)/loser(user)accumulent localement ; rien n'est envoyé tant quesendRewards()(déclenché parfinish()) n'a pas tourné.- Si un viewer est marqué winner et loser, la dernière valeur écrite gagne.
i18n
text(key, args)(global) =kapp.textset.get(key, args). Une valeur detextset.jspeut être une string ou une fonctionargs => string(voirreference/i18n.md).- Si la clé est absente,
text()renvoie la clé elle-même (pas d'erreur) — vérifie tes clés.
prefs / text existent en appli (l'inverse du widget)
- Contrairement au widget,
prefsettext()sont injectés en appli. Ne copie pas du code widget qui les évite.
Prefs panel custom (window.KappappsPrefs)
- Sandbox opaque (
allow-scriptssansallow-same-origin) : ton origine y est'null'— pas delocalStorage/cookies/accès au parent. Tout passe parwindow.KappappsPrefs(voirreference/prefs-panels.md). - CSP durcie
script-src 'self': ni CDN, ni<script>inline, même dans le fichier.htmldéclaré enprefs-panel-index. Ton JS vit dans un fichier séparé chargé en<script src="...">relatif. - Remount à chaque sauvegarde du formulaire de préférences (nouvelle iframe, nouveau
onReady) : ton état JS en mémoire ne survit pas — persiste ce qui compte viaKappappsPrefs.setValue(), jamais dans une variable globale. watch(paths)= préfixe de segment : observer un chemin de groupe couvre déjà tous ses descendants, inutile de lister chaque sous-champ.- Un WU
customPaneln'a aucune valeur propre : ne t'attends pas à lire quoi que ce soit à son propre chemin.