Subscribe to registry changes (a bound CommandScope re-resolves on fire).
Run fn with the listener fan-out suspended, firing ONE notify at close iff anything changed
inside. getVersion() still bumps per change, so synchronous readers (the resolve caches key on
it) are never stale — only the fan-out is coalesced. Nestable: inner batches fold into the
outermost. The bulk paths (registerStatic, a contribution's remove(), catalog seeding)
run through this so a K-command seed wakes each subscriber once, not K times — per-key fan-out
during load-time seeding was a main-thread stall (design/COMMAND-EMIT-PERFORMANCE.md).
Command keys on name whose reduced definition carries tag. The registry counterpart of
CommandScope.collectKeysByTag, so a tag authored in the catalog (e.g. mutator) is still
found by a contributor's cross-cutting rule after the chrome moved off the scope and into the
registry. Order unspecified; deduped.
Open a tracked CommandRegistryContribution on name under contributor — the registry
twin of CommandScope.contribution. The register*Definitions(target) seeders take this as
their target, so moving a seed from register(root) onto a name is a one-argument change.
contributor defaults to the name (one core catalog layer per name); pass a distinct token for a
plugin that must coexist with / override the base.
Find a key's static definition by searching names in prefer order first, then all names — the
lookup a consumer's not-in-context fallback composes (design/COMMAND-DESIGN.md §"Named scopes"): a
toolbar prefers the names it represents, then falls back to any name (so a plugin's command registered
under an unforeseen name still shows). Returns the first match's name + reduced def, or undefined
if no name has the key. This is a pure search — the policy (whether to fall back, the prefer list)
lives in the consumer, never in resolution/dispatch.
Optionalprefer: readonly string[]The registered dynamic contribution components for name (registration order).
Monotonic change counter — a useSyncExternalStore snapshot for consumers that read registry
chrome directly (e.g. command-button entries). Bumps whenever the catalog changes, including the
async re-seed after a language switch (which a language-tag snapshot alone would miss).
Trigger a re-resolution without changing any registration (mirrors CommandScope.invalidate) — e.g. the active language changed, so translator-resolved chrome must re-derive.
All command keys registered on name (deduped; order unspecified).
All names with at least one registered command.
Contribute/replace partial for (name, commandKey) under contributor. Same contributor +
key replaces the existing layer wholesale (idempotent re-seed — no double-accumulation of
tags/disabledReasons); a new contributor pushes a most-specific layer. Bumps listeners.
Register a plugin's dynamic contribution CommandContributionComponent for name — a React
component CommandScopeProvider renders at every live instance of that named scope (so it
inherits the React context there and contributes via props.scope.contribution()). Returns a remover.
Seed a static contributor for name — instance-independent presentation into the catalog, idempotent
by the contributor fn's identity (re-register replaces its layer). The app-load counterpart of a feature
provider seeding its chrome; a plugin calls this to inject static into an existing name.
Remove contributor's layer from (name, commandKey). Bumps listeners iff something changed.
Remove every layer contributor seeded on name, across all command keys. The bulk twin of
removeCommand — used when a whole source drops out of a name (e.g. the catalog service
clearing a source that has no pack for the newly-active language, so a bundled-en floor leaves no
stale chrome behind). Bumps listeners iff something changed.
The reduced, instance-independent definition for (name, commandKey) — the whole layer stack
folded by reduceDefinitions (least→most specific). Read by CommandScope.resolve as
a node's static defaults AND by the catalog (shortcut dialog / palette). undefined if nothing is
registered.
The flat named registry —
Map<name, Map<key, layer-stack>>, parent-less (NOT a tree). It holds only instance-independent, STATIC command definitions (default presentation + any model-free stub) and is the catalog: fully enumerable, seedable at app-init with zero instances mounted.It is the static half of "static contribution + dynamic contribution" (design/COMMAND-DESIGN.md §"Named scopes"): a CommandScope instance carries a
name, and CommandScope.resolve foldsregistry[name]in as the least-specific defaults — but only for a command that also has a live dynamic contribution (a scope's own def). A static entry with no dynamic contributor stays in the catalog (discoverable) yet does not resolve, so its shortcut never binds. This is why there is nocatalogOnly: an inactive scope simply isn't contributing dynamically, so its statics aren't pulled — the editor's idle F4 falls toredofor free, and atablecommand is dormant until a table is active. Element lifecycle ≠ command lifecycle; a mounted scope that stops contributing a key looks, to resolution, exactly like one that unmounted.Names have no hierarchy —
app/workbook/movable/sheet/selection/editoretc. is just typical mounting plus a catalog display-order hint; which name wins at resolve is decided entirely by live instance-tree position, never by the registry.A
(name, key)is a layer stack, not one merged def, so multiple contributors (the core catalog + N plugins) coexist and remove independently. Layers reduce by the same reduceDefinitions policy as the scope chain (override per property; accumulatedisabledReasons/tags), keyed by contributor identity (same → replace, other → push).