Commands and palette

Palette, shortcut, menu, toolbar, console and script all converge on K.commands.execute(). Zone and capability are checked there once and for all, which leaves a single file to read in order to know what can run, and under what conditions.

The command object

K.commands.register({ id: 'phreeqc.simulation.lancer', source: 'phreeqc', title: { fr: 'PHREEQC : lancer la simulation', en: 'PHREEQC: run simulation' }, category: 'geochimie', capability: 'db.write', zone: null, // 'local' restricts it to developer mode keybinding: 'ctrl+alt+p', quickAccess: true, when: function (ctx) { return ctx.get('shell.ready') && ctx.get('selection.count') > 0; }, handler: function (args, ctx) { return fetch('/api/phreeqc/run', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(args) }).then(function (r) { return r.json(); }); } });
FieldTypeDescription
idstringRequired. A command without an identifier throws.
title{fr, en} or keyResolved by K.resolveTitle().
handlerfunction(args, context). Sync or promise. Absent when declarative.
categorystringGrouping in the palette and the Actions panel.
capabilitystringRequired capability. The core is exempt.
zonestring'local' restricts the command to developer mode.
whenfunctionReceives the context service. A thrown exception counts as false.
keybindingstringSuggested combination. The effective binding lives in a separate table.
quickAccessboolOffered in the quick-access section.
declarativeboolDeclared by manifest, handler absent until activation.
when is a function, not a string. It receives the context service and must return a boolean. An exception inside the predicate makes the command unavailable rather than breaking the palette.

Context keys

The context store mirrors the current state of the application, and predicates are evaluated against it. It is open: any view may set a key through K.context.set(), and predicates may read it.

Key set by the kernelValue
kernel.zonedistribution or local.
shell.readyBoolean. Set by shell-dock.js once the docks are mounted.
// Setting a key from a view K.context.set('selection.count', rows.length); // Subscribing var off = K.context.onChange(function (key, value) { if (key === 'selection.count') refresh(); }); off.dispose();

Dispatch sequence

#CheckRejection
1Identifier resolution.Commande inconnue: <id>
2Zone.Commande réservée à la zone locale
3Capability, except for the core.Capacité refusée: <cap>
4when predicate.Commande indisponible dans ce contexte
5Source activation, if dormant.The activation error is propagated.
6Handler present after activation.Commande sans gestionnaire après activation
7command.before, handler, command.after.Any failure emits command.error and rejects.

Activation happens before the handler is resolved: a command declared by manifest has no handler yet, and it is the activation of its source that installs one.

A call from the console or from a script goes through the same seven checks as a click in the interface. No execution path bypasses them, including commands triggered by a workflow or by the assistant.

Listing what is available

// Availability filtering is done by the kernel, so that each view does not have // to reimplement the rule, and therefore drift away from it. K.commands.list(); // available here and now K.commands.list({ quickAccess: true }); K.commands.list({ category: 'geochimie' }); K.commands.list({ source: 'phreeqc' }); K.commands.list({ available: false }); // everything, unavailable included K.commands.isAvailable('phreeqc.simulation.lancer');

Overriding a command

K.commands.override(id, wrapper) wraps a command without touching the original. Disposing the token restores the previous state exactly. This is the pattern used for the CRM extensions, and the primitive that customisations rely on when they have to survive a core update.

var token = K.commands.override('app.donnees.importCSV', function (next, args, ctx) { if (!ctx.get('campagne.active')) { return Promise.reject(new Error('Declare a campaign before importing.')); } return next(args); }); token.dispose(); // the original command is restored exactly

The palette

Ctrl + Shift + P   →   core.palette.open

The palette calls K.commands.list() every time it opens, so it only shows commands available in the current context, with their category and shortcut. A command brought in by a plugin loaded mid-session appears without a restart.

IsoFind command palette Figure 1: The command palette, filtered as you type.

Why a command is missing

CauseCheck
Zone local outside developer mode. K.get('command', id).zone
Capability not granted to the source. K.get('command', id).capability, then the capability provider.
when predicate is false. K.context.all(), then read the predicate.
Source not registered. K.get('command', id) returns null.

The action log

The Journal panel feeds on command.before, command.after and command.error, as well as on lines written through K.log(). It also captures the console.

The journal is a session trace, cleared on restart. It does not replace the signed, chained and tamper-evident registry of sample modifications described in HMAC traceability and integrity. For an audit, the signed registry is what counts.