posit-dev / posit-dev/shinychat

Feature: standalone register_slash_command() for chat_ui() without chat_server()

Open
#273 4 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

ai-triage:done Priority: Medium
Dominant language
TypeScript
Stars
139
Forks
28
Avg merge
23h 44m
Merged PRs (30d)
50

Description

Summary

chat_app.R has a standing TODO to "Support a standalone register_slash_command() that works outside the returned environment (e.g., so callers don't have to thread the return value)." This proposes exactly that.

Motivation

Slash commands are currently only reachable through chat_server()'s $slash_command() method. That has two friction points:

  1. You must thread the returned object around. To register a command from anywhere other than right next to the chat_server() call, you have to capture and pass its return value.
  2. Fully custom chat loops can't use slash commands at all. The Get started vignette says as much: "Slash commands are currently available only through chat_server(), not when building a fully custom chat loop with chat_ui() and chat_append() directly." Apps that drive chat_ui() with their own server logic (custom streaming, an external agent loop) can't offer the official palette.
Proposal

Export a free function that mirrors $slash_command() but is callable anywhere with just the chat id and session:

register_slash_command(
  id, name, description, handler, ...,
  echo = NULL, force = FALSE,
  session = getDefaultReactiveDomain()
)
  • Same handler semantics as $slash_command() (0/1-arg, or NULL for client-side handling via shiny:chat-slash-command); a 1-arg handler receives a ContentSlashCommand.
  • The command registry lives in session$userData keyed by the namespaced id (the same idiom already used for bookmark info in chat_restore.R), and the dispatch + sync observers are set up once, lazily — so no return value needs threading.
  • The slash machinery is factored into an internal helper that chat_server() reuses, so both paths share a single implementation and can't drift.

Follows shinychat's argument conventions (id first, session last, matching chat_append() / chat_clear() / update_chat_user_input()).

Open question

R's $slash_command() returns an unregister closure; I've kept that for register_slash_command(). Would you prefer a separate remove_slash_command(id, name, session) for parity with the Python Chat.remove_slash_command() method, or is returning the unregister function the right R idiom here? Happy to adjust.

I have a draft PR ready that implements this as a pure refactor (no behavior change to chat_server()), with standalone tests and a clean R CMD check.

Contributor guide

No contributing guide indexed for this repository

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with the TODO in chat_app.R and the existing $slash_command() implementation used by chat_server(); inspect chat_restore.R for the session$userData registry pattern. Review the standalone tests mentioned in the proposal and run R CMD check. Done means standalone registration works with chat_ui() and chat_append(), chat_server() continues to behave unchanged, and unregistering remains defined.

Written by the indexing model from the issue text.

Assessment

Tech stack
r
Domain
full-stack
Issue type
Feature
Difficulty
4/5
Estimated time
3-5 days
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.