Composer completion — one config, one popup, one dispatch

What the person sees

In every composer — Blazor, the React portal, the React Native app (iOS, Android, Expo web, the Mac shell) — typing / at the start of the draft opens the commands and skills available here: /harness, /agent, /model, /effort, every skill the viewer can read for this context, and under a CLI harness that harness's own commands (/login, /logout, …) plus /harness. Typing @ anywhere opens nodes and agents. Each row shows its label, its kind and its description. On the web and on a Mac ↑/↓ move the highlight, Enter or Tab pick, Escape closes; on a phone a row is tapped.

Picking a command does what the command does, exactly as submitting it would:

Command What happens
/harness, /agent, /model The picker opens in the composer; the pick rides the next send.
/effort The value picker opens over the choices the /effort skill node declares; the pick is patched onto the open thread's composer (composer.effort) or seeded on the thread the next send starts.
/login, /logout (CLI harness) The harness's sign-in page opens (HarnessLogin, or the device mesh's Claude Code page); the composer says so, or says the harness has no such page here. Never forwarded to the headless CLI.
an instruction skill Picked bare, the draft keeps /name and shows the skill's description; sent with a task, the task is submitted with the load_skill directive (SkillInfo.ToSubmissionText).
an OpenContent / Navigate skill The declared node (or the typed target) opens.
a NewThread skill (/clear) The composer starts a fresh conversation.
any other command under a CLI harness Forwarded to the CLI as the message.
a word no skill answers Sent as a plain message, as before.

One declaration

src/MeshWeaver.AI/Completion/ComposerCompletion.cs declares the configuration:

Blazor reads the class directly: ThreadChatView.GetCompletionConfig takes its trigger characters and BuildHarnessCommandCompletions its harness list from it.

The clients are served the list, never a copy of it. ComposerCommandAutocompleteProvider is registered on every node hub, so the AutocompleteRequest the clients already send to the viewer's own hub for @ answers a /word query too, from the same SkillAutocompleteProvider catalogue Blazor lists (each row keeping its skill node path) or, under a CLI harness, from HarnessCommandItems. For any other query it answers nothing, so it adds nothing to an @ answer. A client keeps only the rows in the Commands category.

The client mirror is drift-checked. clients/react/src/controls/composerCompletion.ts mirrors the scalar config (so the client can debounce and parse without a round trip), the action vocabulary, the built-in map and the instruction-skill submission text. composerCompletion.test.ts reads the C# source and fails when the two disagree. A server whose AI module predates the provider answers a / query with no command row at all; the client then offers only the built-in commands, never a second catalogue.

One popup, one model, one dispatch

Piece Where What it owns
useComposerCompletion clients/react/src/controls/composerModel.ts token tracking (/word at the start, @token anywhere), the debounced, generation-guarded fetch, the highlight, the keys, splice-on-pick
ComposerPopup clients/react/src/controls/composerPopup.tsx rows (label · kind · description), highlight, selected row, loading / failed / empty states, accessibility roles. It imports nothing platform-specific: the web leaf passes DOM primitives (composerPopupDom.tsx), the native leaf View/Pressable/Text (app/react-native/src/rnComposerPopup.tsx)
resolveComposerCommand / runComposerCommand clients/react/src/controls/composerCompletion.ts what a command does — resolved like Blazor's dispatch (CLI harness first, then the skill node read from the picked row's path or found through the server's list) and run through the leaf's handlers
the harness registry clients/react/src/controls/composerHarness.ts the /harness picker's options, now shared by the web and native composers

The pickers a command opens are the same ComposerPopup over the option catalogue. The RN parity suite (app/react-native/src/parity.test.ts) fails if either leaf stops using the shared popup, model or dispatch.

What is not covered here

Tests