Le registre de contributions

Un registre par type, derrière une inscription unique : K.contribute(type, entree). Le type n'est pas fermé, un point de contribution nouveau se déclare sans toucher au noyau. Toute inscription rend un jeton de libération, agrégé par source, ce qui permet de décharger un plugin sans que le noyau connaisse la nature de ses contributions.

Types en usage

TypeAPI de haut niveauSurfaces qui la lisent
commandK.commands.register(spec)Palette, menus, raccourcis, panneau Actions, scripts
panelK.shell.registerPanel(spec)Docks, menu Affichage, sélecteur de panneaux
settingK.settings.declare(spec)Préférences
widgetK.dashboard.registerWidget(spec)Tableau de bord
editorK.editors.register(spec)Onglets de la zone centrale

Les tâches et les connecteurs ne passent pas par contribute : le moteur de workflows tient sa propre table (K.workflows.declarerTache), et le registre de connecteurs la sienne. Tous deux rendent malgré tout un jeton de libération, et émettent un évènement à chaque mutation.

Enveloppe d'une contribution

ChampDescription
idRequis. Une inscription sans identifiant lève.
sourcecore par défaut. Sinon l'identifiant du plugin, ou user.
titleObjet {fr, en}, ou une clé de translations.js si la source est le cœur.
declarativeDrapeau. Contribution issue d'un manifeste, donc connue avant toute exécution.
Deux formes de titre sont acceptées. Un objet {fr, en} quand le contributeur porte ses propres chaines, ce qui est le cas des plugins, qui ne peuvent pas écrire dans le dictionnaire de l'application. Une clé de traduction quand le contributeur est le cœur et que la chaine vit dans translations.js.

Règle de remplacement

Une source peut remplacer sa propre contribution. Le cas visé n'est pas un doublon : un plugin déclare une commande dans son manifeste, puis, une fois activé, enregistre son implémentation réelle sous le même identifiant.

Une source ne peut jamais écraser celle d'une autre. Le noyau écrit un avertissement en console et rend un jeton inerte. Cette règle empêche un plugin de reprendre à son compte la navigation du noyau en déclarant core.go.dashboard.

// Convention d'adressage core.journal.toggle panneau ou commande du noyau app.outils.isofImport commande du domaine (modale) app.donnees.importCSV commande du domaine (modale) workflow.correction-blanc-v3 workflow enregistre, inscrit comme commande phreeqc.simulation.lancer plugin, prefixe par son identifiant user.* script ou macro de la zone locale

Déclaratif contre impératif

Le drapeau declarative distingue ce qui vient d'un manifeste, donc connu avant toute exécution, de ce qui vient de code déjà actif. Une commande déclarative n'a pas de handler : elle apparait dans la palette, et son invocation déclenche le réveil de sa source.

Manifeste Commande visible, handler absent Invocation Activation de la source Handler installé, puis appelé

Si l'activation aboutit sans installer de handler, le dispatch échoue explicitement : Commande sans gestionnaire après activation.

Jetons et libération

var jeton = K.commands.register({ /* ... */ }); jeton.dispose(); // retire cette contribution K.releaseSource('phreeqc'); // retire TOUT ce que la source a inscrit

Chaque jeton est ajouté à un agrégat par source. La libération se fait en ordre inverse d'inscription : une contribution tardive peut dépendre d'une contribution antérieure, jamais l'inverse. releaseSource() remplace le démontage manuel, qui devait énumérer chaque puits et risquait d'en oublier un.

Un jeton non libéré laisse une contribution fantôme après rechargement du plugin : la commande reste dans la palette, son code n'existe plus, le clic échoue sans message. Le shell écoute registry.changed et détruit le panneau correspondant dès que l'entrée disparait du registre.

Évènements

ÉvènementCharge utileÉmis par
registry.changed{type, id, added}Noyau, à chaque mutation.
command.before{id, args}Noyau, après les gardes, avant le handler.
command.after{id, args, result}Noyau, après succès.
command.error{id, args, error}Noyau, sur refus ou échec.
setting.changed{id, value, layer}Réglages.
workflow.tasks.changed{id}Moteur de workflows.
dashboard.changedAucuneRegistre de widgets.

Hooks annulables

K.events.emitBlocking(nom, charge) attend ses abonnés et renvoie false si l'un d'eux a annulé. C'est le point d'injection propre pour les scripts de surcharge, qui peuvent ainsi refuser une sauvegarde ou un lancement de moteur sans toucher au cœur. Les abonnés peuvent être asynchrones.

Réglages en couches

Quatre couches, de la moins prioritaire à la plus prioritaire : default, edition, user, project. La valeur effective est calculée à la lecture, jamais figée à l'écriture : sans cela, une surcharge posée tardivement n'aurait aucun effet sur ce qui a déjà lu.

K.settings.declare({ id: 'phreeqc.timeout', source: 'phreeqc', title: { fr: 'Délai maximal', en: 'Timeout' }, default: 30 }); K.settings.get('phreeqc.timeout'); // couche la plus prioritaire qui la porte K.settings.set('phreeqc.timeout', 60); // couche 'user' par defaut, persistee K.settings.onChange('phreeqc.timeout', fn);

Le menu natif ne suit pas le registre

La barre de menus est native, construite en Rust au démarrage, et ne se reconstruit pas. Le menu Affichage liste les panneaux du noyau, connus à la compilation, puis une entrée Tous les panneaux... qui lit le registre au moment de l'ouverture. C'est le seul endroit où apparaissent les panneaux de plugins.

Le pont Rust vers JS passe par window.IsoFindPanelMenu.activer(rang), qui exécute la commande <id>.toggle correspondante. Les coches sont resynchronisées sur registry.changed et command.after.

Inspecter

K.list('command').length; K.list('panel', function (p) { return p.source !== 'core'; }); K.commands.isAvailable('core.journal.toggle'); Array.from(K.context.all().entries()); // Mode developpeur uniquement await K.commands.execute('core.dev.dump');
Présence au registre et exécutabilité sont distinctes. La zone, la capacité et le prédicat when sont évalués au dispatch, pas à l'inscription. K.commands.isAvailable(id) applique exactement les mêmes règles que la palette.