From 568207687da91ae7c339da2061017e9c542c60ae Mon Sep 17 00:00:00 2001 From: Dave Date: Sat, 14 Mar 2026 18:53:29 +0000 Subject: [PATCH] story-kit: merge 241_story_help_slash_command --- frontend/src/components/Chat.tsx | 10 ++ frontend/src/components/HelpOverlay.tsx | 158 ++++++++++++++++++++++++ 2 files changed, 168 insertions(+) create mode 100644 frontend/src/components/HelpOverlay.tsx diff --git a/frontend/src/components/Chat.tsx b/frontend/src/components/Chat.tsx index 1ad976c..1368ec9 100644 --- a/frontend/src/components/Chat.tsx +++ b/frontend/src/components/Chat.tsx @@ -10,6 +10,7 @@ import { AgentPanel } from "./AgentPanel"; import { ChatHeader } from "./ChatHeader"; import type { ChatInputHandle } from "./ChatInput"; import { ChatInput } from "./ChatInput"; +import { HelpOverlay } from "./HelpOverlay"; import { LozengeFlyProvider } from "./LozengeFlyContext"; import { MessageItem } from "./MessageItem"; import { SideQuestionOverlay } from "./SideQuestionOverlay"; @@ -203,6 +204,7 @@ export function Chat({ projectPath, onCloseProject }: ChatProps) { response: string; loading: boolean; } | null>(null); + const [showHelp, setShowHelp] = useState(false); // Ref so stale WebSocket callbacks can read the current queued messages const queuedMessagesRef = useRef<{ id: string; text: string }[]>([]); const queueIdCounterRef = useRef(0); @@ -475,6 +477,12 @@ export function Chat({ projectPath, onCloseProject }: ChatProps) { const sendMessage = async (messageText: string) => { if (!messageText.trim()) return; + // /help — show available slash commands overlay + if (/^\/help\s*$/i.test(messageText)) { + setShowHelp(true); + return; + } + // /btw — answered from context without disrupting main chat const btwMatch = messageText.match(/^\/btw\s+(.+)/s); if (btwMatch) { @@ -1193,6 +1201,8 @@ export function Chat({ projectPath, onCloseProject }: ChatProps) { )} + {showHelp && setShowHelp(false)} />} + {sideQuestion && ( ", + description: + "Ask a side question using the current conversation as context. The question and answer are not added to the conversation history.", + }, +]; + +interface HelpOverlayProps { + onDismiss: () => void; +} + +/** + * Dismissible overlay that lists all available slash commands. + * Dismiss with Escape, Enter, or Space. + */ +export function HelpOverlay({ onDismiss }: HelpOverlayProps) { + const dismissRef = useRef(onDismiss); + dismissRef.current = onDismiss; + + useEffect(() => { + const handler = (e: KeyboardEvent) => { + if (e.key === "Escape" || e.key === "Enter" || e.key === " ") { + e.preventDefault(); + dismissRef.current(); + } + }; + window.addEventListener("keydown", handler); + return () => window.removeEventListener("keydown", handler); + }, []); + + return ( + // biome-ignore lint/a11y/noStaticElementInteractions: backdrop dismiss is supplementary; keyboard handled via window keydown + // biome-ignore lint/a11y/useKeyWithClickEvents: keyboard dismiss handled via window keydown listener +
+ {/* biome-ignore lint/a11y/useKeyWithClickEvents: stop-propagation only; no real interaction */} + {/* biome-ignore lint/a11y/noStaticElementInteractions: stop-propagation only; no real interaction */} +
e.stopPropagation()} + style={{ + background: "#2f2f2f", + border: "1px solid #444", + borderRadius: "12px", + padding: "24px", + maxWidth: "560px", + width: "90vw", + display: "flex", + flexDirection: "column", + gap: "16px", + boxShadow: "0 8px 32px rgba(0,0,0,0.5)", + }} + > + {/* Header */} +
+ + Slash Commands + + +
+ + {/* Command list */} +
+ {SLASH_COMMANDS.map((cmd) => ( +
+ + {cmd.name} + + + {cmd.description} + +
+ ))} +
+ + {/* Footer hint */} +
+ Press Escape, Enter, or Space to dismiss +
+
+
+ ); +}