Command Archetypes
Which kind of interaction a command should be, before anything is built. Every TomoriBot command follows one of eight archetypes, and each has a reference command to copy. Wiring is in Adding a Panel; text and layout are in Panel Prose and Layout.
Choose an archetype
Section titled “Choose an archetype”| Archetype | Use when | Reference |
|---|---|---|
| Categorized Control Panel | A root owns several related settings areas that users revisit and compare | /moderation; also /config, /personal config, /memories, /personal memories |
| Collection Panel | The pages are saved entities the user created, plus a way to add one | /providers, /personal providers; inside /config, the MCP servers and SillyTavern presets pages |
| Entity Workspace | The user needs two axes at once: which entity, and which aspect of it | /config Persona category (persona selector plus nine pages) |
| Paginated Reference | Read-only information users browse repeatedly, rebuilt from definitions or stored data | /help, /status, /stats |
| Guided Wizard | Ordered choices, later steps depend on earlier ones, nothing is saved until the end | /setup |
| Direct Command Family | The user knows the operation, and slash options can name the target safely | /impersonate; also /persona, /generate, /export, /import, /tool |
| Immediate Action | Running the command is the whole intent, with no state worth showing | /respond, /kill, /compact, /ping, /punish, /reward |
| Destructive Action | The operation deletes broad state, removes something irreversibly, or replaces configuration in bulk | /reset config; in panels, the remove-prompt / remove-confirm / remove-cancel routes |
The three panel archetypes share one system (catalog, builder, routes). They differ only in how the active page is chosen: by category, by saved entity, or by entity plus page.
When not to make a panel
Section titled “When not to make a panel”- Two or three self-explanatory operations, such as
/matrix linkand/matrix unlink. - Generation with clear parameters that runs once.
- A one-off diagnostic or link.
- Lifecycle commands whose purpose is creating, importing, or removing an entity (
/persona create). - A destructive action whose safety comes from confirmation. Browsing adds nothing to it.
Do not add a panel only to remove one slash path.
Panel rules
Section titled “Panel rules”These apply to all three panel archetypes.
- Navigation never writes. Category buttons, page selects, and entity selects only change what is shown. A select’s default marks the current page. Writes happen through an explicit button or a modal submission, and end with a receipt and refreshed state.
- Categories: at most five top-row buttons, the active one
Primary. More areas than that belong in a page select within a category. - Rebuild state on every click. Reload from the database and resolve stable IDs within the current scope. Trust nothing from the old message: names, list positions, select defaults. When a selected entity has been removed, fall back to a fixed choice (the main persona, the neighbouring entry, or the add page) and say so.
- Reads can fail. Distinguish an empty collection from a failed read. A stale read may still show
saved values with a warning, a
Retrybutton, and writes disabled; a failed read with nothing to show is a retryable failure and must never render as empty. - Receipts: after a write, repaint the saved state with a receipt. A no-op submit gets an
inforeceipt. A failed write keeps the previous state on screen and never shows the attempted change.
Collection Panels
Section titled “Collection Panels”- The entity select lists saved entities with stable database IDs as option values and localized names as labels. The add action is its first option.
- Selecting an entity opens its page. Activating, enabling, or removing it takes a separate button.
- Past 25 entities, page the select (
resolveRangeSelection()andbuildRangeSelectOptions()); never truncate or merge labels. - A panel that serves two ownership scopes with one builder names the scope in its title, as
/providers(Server Providers) and/personal providers(Personal Providers) do.
Entity Workspaces
Section titled “Entity Workspaces”- Switching page keeps the entity; switching entity keeps the page.
- The main persona is the first selection and is labelled as the main persona.
Recorded exception
Section titled “Recorded exception”The SillyTavern presets select activates the chosen preset directly, and None disables presets. Only
one preset can be active and another selection reverses it, so a preview step would add a click to the
select’s only purpose. The select is disabled on a stale read, re-authorizes before writing, and still
repaints with a receipt; deleting a preset keeps the normal button and confirmation.
Other departures need a verified Discord limit, a different archetype, or a recorded product decision, added here.
Destructive actions
Section titled “Destructive actions”Confirmation scales with the damage: removing one recoverable entry needs less than resetting a server.
- Resolve the exact target and authorize the actor.
- Show localized impact text naming the target and what is lost.
- Require an explicit confirm button. Resets of a whole workspace also need a high-friction check.
- Re-resolve the target and re-authorize after confirmation.
- Run the repository operation once; repeated or stale clicks must not run it again.
- Invalidate caches only after success.
- Show a receipt naming what was removed and whether an export can restore it.
Selecting a page, selecting an entity, or a pre-checked checkbox is never consent to destroy. After a removal, drop the removed ID from the panel state, reload the collection, and select a fixed neighbour or the add page.
/nuke still confirms through a required yes/no slash option; moving it to a button confirmation is
open work.
Guided wizards
Section titled “Guided wizards”- Keep a typed draft that holds only this invocation’s choices.
/setupkeeps drafts in memory (setupDraftStore.ts), where a restart discards them; use a durable draft only when resuming across restarts is a product requirement. - Route every control through the global registry, with no collectors. The only expiry is the draft’s own lifetime, checked on each click.
- Cancel never saves anything, and a repeated submission cannot create or charge twice.
- Switching between mutually exclusive modes replaces the draft and discards the old mode’s credential.
- Secrets never appear in review text, logs, custom IDs, or errors.
- The final receipt repaints the wizard’s own message and has no controls.
Direct commands and immediate actions
Section titled “Direct commands and immediate actions”- Use native slash options for users, channels, roles, attachments, booleans, numbers, and short choice lists. Use autocomplete for database entities, returning stable IDs, and validate the submitted value again: a suggestion does not authorize anything.
- Open a modal only for input too long or too structured for slash options.
- Check context and permissions quickly, then defer before any database, provider, or history work.
- Add confirmation only where the consequence calls for it.
- When a command is renamed, keep the established verb (
/killoverstop) and mention the old wording in the description and/help.
Visibility and authorization
Section titled “Visibility and authorization”| Mode | Level | Mechanism | Use |
|---|---|---|---|
| 1. Hide from discovery | Command | guildOnly or managerOnly exported by the command module, applied by applyRootCommandRestrictions() in src/utils/discord/commandLoader.ts | The whole root is ineligible for the same actors |
| 2. Deny on execution | Command | A check in execute() with a localized denial | Mixed roots, and always underneath mode 1 |
| 3. Deny on interaction | Panel | Re-resolve and re-authorize in the route handler | Every button, select, and modal submission |
| 4. Filter or disable | Panel | Leave the control out, or render it disabled | See below |
Modes 2 and 3 are always on. setContexts(Guild) is a platform guarantee, so a guild-only command may
assume a guild. setDefaultMemberPermissions(ManageGuild) is only a default that server admins can
override per role and channel, so a handler never treats being called as permission.
- Disabled or omitted: disable a control for a temporary state or an unmet prerequisite (Add at the
limit, writes during a stale read), or to show a member which manager-only actions exist. Omit it when
it cannot apply in this context, when its existence or state is sensitive, or when the component
cannot be disabled (a single select option).
/configis registered without a Manage Server default so members can reach the Persona category, and manager-only actions appear there disabled.
Done means
Section titled “Done means”- Every operation the panel absorbed has a place in it, and every current value and override can be found.
- Writes use the existing validation and repository path and invalidate the same caches.
- Destructive actions follow the section above, and disabled-versus-omitted is applied the same way throughout.
- Stale selections and changed permissions are safe.
- Behaviour past 25 entities is tested.
- Every locale covers navigation, actions, receipts, and denials.