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:
@@ -4,7 +4,8 @@
|
||||
//
|
||||
// Also exposes a tiny registry of currently-mounted terminal panes so the
|
||||
// MessageBubble context menu can list them. TerminalPane registers on mount,
|
||||
// unregisters on unmount.
|
||||
// unregisters on unmount. v1.10.4 adds a parallel ChatInput registry used by
|
||||
// the terminal floating menu's "Send to chat" submenu.
|
||||
|
||||
type Listener<T> = (payload: T) => void;
|
||||
|
||||
@@ -41,12 +42,25 @@ export interface SendToTerminalPayload {
|
||||
|
||||
export const sendToTerminal = createEvent<SendToTerminalPayload>();
|
||||
|
||||
// v1.10.4: reverse direction. Terminal floating menu "Send to chat" emits this
|
||||
// with the target chat's chat_id; ChatInput subscribes and appends to its draft.
|
||||
export interface SendToChatPayload {
|
||||
chat_id: string;
|
||||
text: string;
|
||||
}
|
||||
|
||||
export const sendToChat = createEvent<SendToChatPayload>();
|
||||
|
||||
export interface TerminalRegistration {
|
||||
paneId: string;
|
||||
label: string;
|
||||
// v1.10.3 kbd-shortcuts: Cmd+` needs to focus the active terminal's xterm
|
||||
// input layer. TerminalPane binds this to term.focus().
|
||||
focus: () => void;
|
||||
// v1.10.4: Cmd+F opens the search bar over the active terminal. Workspace
|
||||
// also binds a "Paste" button in the terminal pane header to paste().
|
||||
openSearch: () => void;
|
||||
paste: () => void;
|
||||
}
|
||||
|
||||
const terminalRegistry = new Map<string, TerminalRegistration>();
|
||||
@@ -63,8 +77,14 @@ function notifyRegistry(): void {
|
||||
}
|
||||
|
||||
export const terminalsRegistry = {
|
||||
register(paneId: string, label: string, focus: () => void): () => void {
|
||||
terminalRegistry.set(paneId, { paneId, label, focus });
|
||||
register(
|
||||
paneId: string,
|
||||
label: string,
|
||||
focus: () => void,
|
||||
openSearch: () => void,
|
||||
paste: () => void,
|
||||
): () => void {
|
||||
terminalRegistry.set(paneId, { paneId, label, focus, openSearch, paste });
|
||||
notifyRegistry();
|
||||
return () => {
|
||||
terminalRegistry.delete(paneId);
|
||||
@@ -84,3 +104,48 @@ export const terminalsRegistry = {
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
// v1.10.4: parallel registry of mounted ChatInput components so the terminal
|
||||
// floating menu's "Send to chat" submenu can list open chats. Mirrors
|
||||
// terminalsRegistry exactly; same subscriber pattern.
|
||||
export interface ChatInputRegistration {
|
||||
chatId: string;
|
||||
label: string;
|
||||
focus: () => void;
|
||||
}
|
||||
|
||||
const chatInputRegistry = new Map<string, ChatInputRegistration>();
|
||||
const chatInputListeners = new Set<Listener<void>>();
|
||||
|
||||
function notifyChatInputs(): void {
|
||||
for (const l of chatInputListeners) {
|
||||
try {
|
||||
l();
|
||||
} catch {
|
||||
/* ignore */
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const chatInputsRegistry = {
|
||||
register(chatId: string, label: string, focus: () => void): () => void {
|
||||
chatInputRegistry.set(chatId, { chatId, label, focus });
|
||||
notifyChatInputs();
|
||||
return () => {
|
||||
chatInputRegistry.delete(chatId);
|
||||
notifyChatInputs();
|
||||
};
|
||||
},
|
||||
list(): ChatInputRegistration[] {
|
||||
return Array.from(chatInputRegistry.values());
|
||||
},
|
||||
get(chatId: string): ChatInputRegistration | undefined {
|
||||
return chatInputRegistry.get(chatId);
|
||||
},
|
||||
subscribe(listener: Listener<void>): () => void {
|
||||
chatInputListeners.add(listener);
|
||||
return () => {
|
||||
chatInputListeners.delete(listener);
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user