Skip to content

Pièges spécifiques aux applis ​

Pièges transverses (V1→V2, async, noms de services, arguments chat) : voir common-PITFALLS.md dans le corpus téléchargé. Ci-dessous, ce qui est propre au runtime appli.

Cycle de vie ​

  • back-task et launch-mode: backtask sont à retirer avant toute nouvelle soumission, même dans un ancien brouillon. Déclarer un PROCESSUS dans les defaults si le démarrage automatique est voulu ; voir la migration du manifeste.
  • kapp.finish() est explicite. Une appli ne se termine pas quand script.js finit de s'exécuter (contrairement au widget). Si tu oublies finish(), l'appli reste ouverte et les rewards accumulées ne sont jamais envoyées (sendRewards() est appelé par finish()).
  • Pas de window.executeAppFunction : ton code tourne dans script.js après l'event AppliLoaded (généralement une IIFE (async () => { ... })()).
  • kapp.display() affiche la fenêtre ; pour une appli sans visuel, tu n'en as pas besoin.
  • Nettoyage avant sortie : kapp.onBeforeExit('*', fn) pour les ressources communes. Les hooks sélectionnés tournent en parallèle, avec 10 s pour le groupe ; ce n'est pas une sauvegarde garantie en cas de crash. Verrouille les actions avant le premier await et traite l'échec des récompenses avant fermeture. Voir cycle de vie.

Transports et plugins ​

  • onReady confirme le bridge de la page, pas la disponibilité de l'appli. Un nouveau viewer doit demander l'état ; data() ne lit pas la mémoire de la partie. Voir protocole.
  • Un succès HTTP n'est pas une confirmation métier. emitWithReply attend reply explicite ; après timeout, vérifie l'état avant tout renvoi. requestId assure la corrélation, pas la déduplication métier.
  • Plusieurs viewers, onglets et admins peuvent agir ensemble. Les callbacks async ne sont pas attendus par le bus d'événements. Réserve une action avant une attente, borne les files et désabonne les handlers à la sortie. Voir transports.

Stockage : ne pas confondre les trois ​

  • Le helper V3 et les stockages V2 se migrent ensemble. KappappsHost.data() lit V2 seulement avec helper-v3.js ; les helpers V1/V2 restent sur V1. Pas de méthode publique dataV2(). Charger le V3 par URL absolue Kappapps, jamais via un fallback Bunny.
  • Ne pas confondre taille, file et quota. Le corps JSON complet de emit() est limité à 16 384 octets UTF-8, pas 100 Kio ni 16 384 caractères. Le V3 rejette avec PAYLOAD_TOO_LARGE, QUEUE_FULL, RATE_LIMITED, etc. Traiter error.code, pas le texte ou un ancien jqXHR ; aucun retry manuel aveugle après timeout/réseau.
  • kapp.preferences = config du streamer (définie via preferences.config.js, éditée dans le dashboard). Le SDK remplace prefs au rafraîchissement : une ancienne référence reste ancienne. Applique les valeurs au démarrage, puis relis dans EVENTS.UPDATED ; voir la recette.
  • kapp.dataStorageV2 = état global de l'appli par channel. set(data) écrase tout : relis et fusionne toi-même. Les objets/listes restent distincts, même imbriqués.
  • kapp.userDataStorageV2.get/set = objet par viewer, jamais une liste racine. Les anciens user.getAppData() / setAppData() restent sur V1 et sont dépréciés.
  • userDataStorageV2.reset(labels) exige une liste non vide : champs configurés remis à leur défaut, champs libres ciblés supprimés. resetAll() efface les viewers accessibles, défauts recréés au prochain get(user.id). Les viewers ayant bloqué l'appli sont exclus.
  • kapp.logsStorageV2 = historique empilé via add(), objets ou listes. La copie initiale conserve les dates V1.
  • V1 et V2 sont séparées après la copie initiale. Un reset V2 ne réimporte rien. Ouvrir un brouillon ne migre rien ; publier ne copie pas les sauvegardes développeur. Voir le guide et CHANGELOG.
  • Pas de données importantes dans localStorage, sessionStorage ou IndexedDB. L'origine varie entre plateforme, localhost et CDN. Persiste avec kapp.dataStorageV2 / kapp.userDataStorageV2, et la configuration avec kapp.preferences.

Préférences ​

  • preferences.config.js est compilé en config/preferences.config.json par 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) renvoie undefined si le chemin n'existe pas — garde tes valeurs par défaut.
  • Distingue installation initiale, ajout d'élément et mise à jour : défauts et prototypes, migrations. Un source: 'path' utilise la clé path, pas value.
  • @parent appartient aux chemins de formulaire ; chaque niveau remonte un groupe, pas un conteneur de collection.
  • Un défaut mediaFile doit désigner un média public portable entre channels ; la lecture rend une valeur hydratée, avec un fichier éventuellement absent. Voir médias et placements.

Rewards ​

  • kapp.rewards.winner(user) / loser(user) accumulent localement ; rien n'est envoyé tant que sendRewards() (déclenché par finish()) n'a pas tourné.
  • Si un viewer est marqué winner et loser, loser l'emporte, quel que soit l'ordre des appels : les perdants sont appliqués après les gagnants lors de l'envoi.

i18n ​

  • text(key, args) (global) = kapp.textset.get(key, args). Une valeur de textset.js peut être une string ou une fonction args => string (voir reference/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, prefs et text() sont injectés en appli. Ne copie pas du code widget qui les évite.

Prefs panel custom (window.KappappsPrefs) ​

  • Sandbox opaque (allow-scripts sans allow-same-origin) : ton origine y est 'null' — pas de localStorage/cookies/accès au parent. Tout passe par window.KappappsPrefs (voir reference/prefs-panels.md).
  • Entrée HTML obligatoire : prefs-panel-index et wuOptions.source pointent vers un .html/.htm, jamais directement vers un .js. La plateforme ne génère pas de coquille HTML.
  • CSP durcie script-src 'self' https://kappapps.app : seul le SDK prefs officiel peut venir de Kappapps ; aucun autre CDN ni <script> inline. Le JS de l'app vit dans un fichier séparé chargé avec un chemin 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 via KappappsPrefs.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 customPanel n'a aucune valeur propre : ne t'attends pas à lire quoi que ce soit à son propre chemin.

Apps compilées : publie un artifact autonome ​

  • Si l'app utilise un bundler ou un transpileur, configure cli-config.json avec "runtime": { "root": "dist" } : les sources restent dans le workspace et seul le runtime généré est servi et publié.
  • dist/ doit fonctionner seul : index.html, JS/CSS compilés, assets et config nécessaires. Aucun fichier publié ne doit dépendre de src/, de node_modules/ ou d'un chemin situé hors de dist/.
  • Préfère un bundle navigateur autonome quand le découpage en modules ou chunks au runtime n'apporte rien. Ces imports restent supportés, mais ils multiplient les fichiers et requêtes à servir ; conserve-les seulement quand leur chargement différé a un bénéfice réel.
  • Ne modifie jamais dist/ à la main. Lance explicitement le build avant prepare_preview, un diff ou un push : le DEV HUB ne lance pas les commandes npm du projet automatiquement.

Assets image : évite une multitude de fichiers ​

  • Chaque fichier image chargé produit une requête. En production, les fichiers propres à l'app peuvent être servis directement par le CDN ; en local, ils viennent du DEV HUB. Ne dépends donc jamais de l'origine courante ni d'un chemin racine Kappapps pour les retrouver : utilise des chemins relatifs au runtime publié ou les URLs fournies par le SDK.
  • Regrouper de nombreuses images reste une bonne pratique web : cela réduit le nombre de requêtes navigateur et de fichiers à charger, même lorsque Bunny sert directement les octets.
  • Dès qu'une appli contient beaucoup d'images, utilise Créer un atlas dans l'onglet Assets du DEV HUB ou le tool MCP create_image_atlas pour réduire les requêtes. Voir reference/image-atlases.md.
  • Ne valide jamais un plugin en ouvrant seulement son localhost : sans le shell de prepare_preview, il manque le bridge, les JWT et les sockets, et le test peut être faussement vert.
  • Un atlas ne réduit pas automatiquement le poids : analyse-le avec la qualité WebP, le resize et le profil adaptés, et traite l'avertissement s'il devient plus lourd.
  • Le créateur d'atlas conserve toujours les sources. Après migration du code, supprime-les ou exclue-les du push ; sinon le gain de distribution sera incomplet.