v1.10.4: booterm mobile UX — copy/paste, swipe-close, send-to-chat, search
- Long-press selection + floating menu (mobile + desktop right-click): Copy, Paste, Select All, Search, Send to chat. Tap-outside / Esc dismiss. - Pane-header Paste button (📋) for iOS user-gesture clipboard read. - Swipe-left-to-close on mobile pane pill with red "Close" overlay and translateX visual hint; spring-back below 80px threshold. - Send-to-chat reverse path: chatInputsRegistry + sendToChat event mirror the existing terminalsRegistry pattern. ChatInput appends with newline separator on receive and focuses (no auto-send). - Scrollback search via xterm-addon-search@^0.13.0: SearchBar overlay with N-of-M match counter (onDidChangeResults), Enter/Shift-Enter cycling. - Cmd/Ctrl+F intercept in Session.tsx when active pane is terminal; xterm also intercepts when focused. Browser native find passes through elsewhere. - terminalsRegistry signature extended with openSearch + paste callbacks. Includes deferred CLAUDE.md updates documenting v1.10/v1.10.1/v1.10.2/v1.10.3 learnings (uid 1000 collision, libc match, two event buses, vite proxy order, mobile pane URL sync, xterm canvas selection). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -25,6 +25,7 @@ import { AgentPicker } from '@/components/AgentPicker';
|
||||
import { SkillSlashCommand } from '@/components/SkillSlashCommand';
|
||||
import { api } from '@/api/client';
|
||||
import { sessionEvents } from '@/hooks/sessionEvents';
|
||||
import { chatInputsRegistry, sendToChat } from '@/lib/events';
|
||||
import { useSkills } from '@/hooks/useSkills';
|
||||
import { useViewport } from '@/hooks/useViewport';
|
||||
|
||||
@@ -51,9 +52,16 @@ interface Props {
|
||||
// empty). Callers wire this to api.chats.skillInvoke. Omitting the prop
|
||||
// disables slash-command dispatch (input is sent as literal text).
|
||||
onSlashCommand?: (skillName: string, userMessage: string) => void | Promise<void>;
|
||||
// v1.10.4: send-to-chat reverse path. When chatId is provided, this input
|
||||
// registers in chatInputsRegistry so the terminal floating menu can list
|
||||
// it, and subscribes to sendToChat events scoped to this chatId. Receiving
|
||||
// an event appends the text to the current draft (with a newline separator
|
||||
// when non-empty) and focuses — no auto-send.
|
||||
chatId?: string;
|
||||
chatLabel?: string;
|
||||
}
|
||||
|
||||
export function ChatInput({ disabled, projectId, agentId, onAgentChange, sessionId, webSearchEnabled, onSend, onForceSend, onSlashCommand }: Props) {
|
||||
export function ChatInput({ disabled, projectId, agentId, onAgentChange, sessionId, webSearchEnabled, onSend, onForceSend, onSlashCommand, chatId, chatLabel }: Props) {
|
||||
const { isMobile } = useViewport();
|
||||
const [value, setValue] = useState('');
|
||||
const [busy, setBusy] = useState(false);
|
||||
@@ -107,6 +115,35 @@ export function ChatInput({ disabled, projectId, agentId, onAgentChange, session
|
||||
});
|
||||
}, []);
|
||||
|
||||
// v1.10.4: register this input in the chat-input registry so the terminal
|
||||
// pane's "Send to chat" menu can list it. Re-registers when chatLabel
|
||||
// changes (e.g. rename) so the menu reflects the current name.
|
||||
useEffect(() => {
|
||||
if (!chatId) return;
|
||||
return chatInputsRegistry.register(chatId, chatLabel ?? 'Chat', () => {
|
||||
textareaRef.current?.focus();
|
||||
});
|
||||
}, [chatId, chatLabel]);
|
||||
|
||||
// v1.10.4: subscribe to send_to_chat events scoped by chatId. Appends the
|
||||
// payload text to the current draft (with a newline separator if the
|
||||
// draft is non-empty) and focuses the textarea. Does NOT auto-submit.
|
||||
useEffect(() => {
|
||||
if (!chatId) return;
|
||||
return sendToChat.subscribe(({ chat_id, text }) => {
|
||||
if (chat_id !== chatId) return;
|
||||
setValue((prev) => (prev.length === 0 ? text : `${prev}\n${text}`));
|
||||
requestAnimationFrame(() => {
|
||||
const ta = textareaRef.current;
|
||||
if (!ta) return;
|
||||
ta.focus();
|
||||
// Put caret at end so the user can keep typing immediately.
|
||||
const end = ta.value.length;
|
||||
ta.selectionStart = ta.selectionEnd = end;
|
||||
});
|
||||
});
|
||||
}, [chatId]);
|
||||
|
||||
function removeAttachment(id: string) {
|
||||
setAttachments(prev => prev.filter(a => a.id !== id));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user