reactor-commanding
Skill839 starsChanged 6 days agoMITView on GitHub
Reactor commanding — actions that appear in multiple surfaces (menu + toolbar), need keyboard shortcuts, or need `CanExecute`. `Command`, `StandardCommand`, `UseCommand`, `CommandHost`. Use when wiring shared actions or shortcuts.
In this repository23 other files it gives its agents.
- AGENTS.mdAGENTS.md
- analyzer-dymSkill
- coverage-upliftSkill
- perf-compareSkill
- pr-lifecycleSkill
- pr-reviewSkill
- reactor-advancedSkill
- reactor-asyncSkill
- reactor-build-and-checkSkill
- reactor-chartsSkill
- reactor-designSkill
- reactor-devtoolsSkill
- reactor-dockingSkill
- reactor-dslSkill
- reactor-formsSkill
- reactor-getting-startedSkill
- reactor-inputSkill
- reactor-navigationSkill
- reactor-recipesSkill
- reactor-windowingSkill
- reactor-wrappingSkill
- reactor-appSkill
- ChartingSkill
The file
We split this skill into the rules it gives an agent
7 rules. About 0 are common, 0 are distinctive, and 7 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈0
Distinctive · estimate
This author's own choices. Worth a look.
7
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Very specific: line 8
Use Command when an action shows up in multiple surfaces (toolbar + menu + context menu), needs a keyboard shortcut, or needs CanExecute …
- Very specific: line 10
Use a bare Action for one-off button clicks with no reuse.
- Very specific: line 129
Call unconditionally — don't wrap in if.
- Very specific: line 202
Don't create commands inside loops.
- Very specific: line 204
Don't UseCommand for sync-only commands — it wastes hook slots.
- Very specific: line 205
Don't call UseCommand conditionally — hooks must run in the same order every render.
- Very specific: line 207
Don't mix Execute and ExecuteAsync on the same command — pick one.
≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
Commanding in Reactor
Use Command when an action shows up in multiple surfaces (toolbar + menu +
context menu), needs a keyboard shortcut, or needs CanExecute disabling.
Use a bare Action for one-off button clicks with no reuse.
Controlled prop note: command examples may host controlled inputs such as
TextBox(text, onChange). Factory call sites stay plain-valued, but direct reads from migrated element records areOptional<T>; seemigration/050-optional-t.md.
Command record
var save = new Command
{
Label = "Save", // required
Execute = () => Save(), // sync
// OR:
ExecuteAsync = async () => await SaveAsync(), // async (wrap with UseCommand)
CanExecute = hasChanges, // default true
Icon = SymbolIcon("Save"),
Description = "Save the document", // tooltip + a11y
Accelerator = Accelerator(VirtualKey.S, VirtualKeyModifiers.Control),
AccessKey = "S", // Alt+key
DebounceMs = 0, // >0 = leading-edge debounce (needs UseCommand)
};
// Computed: IsEnabled = CanExecute && !IsExecuting && !IsDebouncingCommand<T> is identical but Execute/ExecuteAsync receive a typed
parameter — bind the parameter at the call site with MenuItem(cmd, item).
StandardCommand factory
Pre-built commands with correct labels, icons, and accelerators:
var cut = StandardCommand.Cut(() => CutSelection());
var copy = StandardCommand.Copy(() => CopySelection());
var paste = StandardCommand.Paste(() => PasteFromClipboard());
var undo = StandardCommand.Undo(() => Undo());
var redo = StandardCommand.Redo(() => Redo());
var delete = StandardCommand.Delete(() => DeleteSelected());
var save = StandardCommand.Save(async () => await SaveAsync()); // async overload
var open = StandardCommand.Open(() => OpenFile());
// CanExecute parameter:
var cut2 = StandardCommand.Cut(() => CutSelection(), canExecute: hasSelection);Also available: SelectAll, Close, Share, Play, Pause, Stop,
Forward, Backward.
Command-aware DSL overloads
Define once, bind anywhere:
var save = StandardCommand.Save(() => SaveFile());
Button(save) // label → content, execute → click, isEnabled → isEnabled
AppBarButton(save) // + icon, accelerator, accessKey, description
MenuItem(save) // + icon, accelerator, accessKey, description
MenuItem(deleteCmd, item) // parameterized: binds item as argumentCustom content (icon + label, stacked layouts) — build the element, then
attach the command with the .Command() modifier. Works on Button,
HyperlinkButton, RepeatButton, ToggleButton, and AppBarButton:
// Factory takes a text label only:
Button(save)
// .Command() binds execute + isEnabled + icon/accelerator/accessKey/description
// onto any custom-content clickable, and auto-disables while !command.IsEnabled:
Button(HStack(Icon(SymbolIcon("Save")), TextBlock("Save"))).Command(save).Command() re-applies IsEnabled on every update (so UseCommand
toggling IsExecuting, or CanExecute changes, flow through) — never
re-thread .IsEnabled(command.IsEnabled) by hand. It composes with
.IsDisabledFocusable(): a disabled command keeps the button reachable
via Tab instead of dropping it from the tab order.
Per-site overrides with with:
var delete = StandardCommand.Delete(() => DeleteSelected());
MenuItem(delete) // "Delete"
MenuItem(delete with { Label = "Remove permanently" })
AppBarButton(delete with { Icon = SymbolIcon("Clear") })UseCommand — async lifecycle
Only needed for commands with ExecuteAsync. Sync commands pass through
unchanged.
class Editor : Component
{
public override Element Render()
{
var saveCmd = UseCommand(StandardCommand.Save(async () =>
{
await SaveAsync();
}));
// saveCmd.Execute is now a sync wrapper around the async
// saveCmd.IsExecuting is true while the async is in-flight
// saveCmd.IsEnabled auto-flips to false while executing
return HStack(
Button(saveCmd),
saveCmd.IsExecuting ? ProgressRing() : Empty());
}
}- Consumes 2 hook slots; re-entrance guard ignores clicks while executing.
IsExecutingresets to false even ifExecuteAsyncthrows.- Call unconditionally — don't wrap in
if. DebounceMs > 0adds leading-edge debounce: the first fire runs, re-fires within the window are dropped, andIsEnabledis false for the duration so the button auto-disables then re-enables. Replaces theTask.Delay-to-absorb-double-clicks workaround. NeedsUseCommandto persist the window — a rawnew Command { DebounceMs = … }bound directly does not debounce. Works on syncExecutetoo (stays on the UI thread; noTask.Runhop). For async commands the disabled window is the longer of the lambda lifetime andDebounceMs.
CommandHost — keyboard-scoped accelerators
Limits Accelerator registration to a subtree:
var save = StandardCommand.Save(() => SaveFile());
var undo = StandardCommand.Undo(() => UndoAction());
CommandHost([save, undo],
VStack(
TextBlock("Ctrl+S / Ctrl+Z only fire inside this region"),
TextBox(value, onChange)))Commands without an Accelerator are ignored by CommandHost.
Sharing commands via Context
Editor-provides / toolbar-consumes:
record EditorCommands(Command Save, Command Undo, Command Redo);
static readonly Context<EditorCommands?> EditorCtx = new(null);
class Editor : Component
{
public override Element Render()
{
var save = UseCommand(StandardCommand.Save(async () => await SaveAsync()));
var undo = StandardCommand.Undo(() => Undo());
return TextBox(text, onChange)
.Provide(EditorCtx, new EditorCommands(save, undo, redo));
}
}
class Toolbar : Component
{
public override Element Render()
{
var cmds = UseContext(EditorCtx);
if (cmds is null) return Empty();
return CommandBar(primaryCommands: [
AppBarButton(cmds.Save),
AppBarButton(cmds.Undo),
]);
}
}ICommand interop
Bridge existing MVVM/CommunityToolkit ICommand:
var cmd = CommandInterop.FromCommand(
viewModel.SaveCommand,
"Save",
icon: SymbolIcon("Save"),
accelerator: Accelerator(VirtualKey.S, VirtualKeyModifiers.Control));Anti-patterns
- Don't create commands inside loops. Define once, bind per item with
MenuItem(cmd, item). - Don't
UseCommandfor sync-only commands — it wastes hook slots. - Don't call
UseCommandconditionally — hooks must run in the same order every render. - Don't mix
ExecuteandExecuteAsyncon the same command — pick one.
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

