Five issues + keyboard shortcuts across booterm and the workspace shell. Auto-switch on create (mobile): addSplitPane now returns the new pane id; Session.tsx wraps it with addPaneAndSwitch which pushes ?pane=<newId> on mobile so the URL-sync effect doesn't fight the just-set activePaneIdx. NewPaneMenu uses the wrapper; desktop Split dropdown is unaffected. Tab-away reconnect: TerminalPane has a connect()/manualReconnect() state machine. ws.onclose backs off 500ms/1s/2s × 3 attempts, then surfaces a [Disconnected] banner with a Reconnect button. visibilitychange listener calls manualReconnect when the tab returns and the WS isn't OPEN. tmux session persists server-side so scrollback is intact on resume. Copy/paste: attachCustomKeyEventHandler binds Cmd/Ctrl-C (copy if selection, else send ^C), Cmd/Ctrl-Shift-C (always swallow — copy if any, no-op otherwise — never sends ^C), Cmd/Ctrl-V and Cmd/Ctrl-Shift-V (navigator.clipboard.readText → ws.send). No custom right-click menu — browser's native menu is preserved. Scroll: removed `set -g mouse on` from tmux.conf so xterm.js sees wheel and touch events natively. scrollback: 10_000, fastScrollModifier: 'shift', altClickMovesCursor: false. Container has touch-action: pan-y for mobile. Right-edge gap: inline <style> overrides xterm's defaults to width:100% height:100% and hides the scrollbar chrome. Host container is flex-1 min-w-0 self-stretch w-full. Three refit triggers: ResizeObserver (rAF-wrapped), document.fonts.ready, and useEffect on the new active prop. Background color matched between outer div, inner div, and xterm theme. Keyboard shortcuts in Session.tsx (window-level keydown): Cmd/Ctrl+` focus active terminal, else jump to last Cmd/Ctrl+Shift+T new terminal pane Cmd/Ctrl+Shift+C new chat pane (defers to xterm copy if focused) Cmd/Ctrl+W close active pane Cmd/Ctrl+Tab/Shift+Tab cycle next / prev pane Cmd/Ctrl+1..9 jump to pane N terminalsRegistry gains a focus() callback per registration so Cmd+` can call term.focus() on the active terminal.
87 lines
2.2 KiB
TypeScript
87 lines
2.2 KiB
TypeScript
// Minimal pub/sub for ephemeral UI events that don't belong on the sessionEvents
|
|
// bus (sessionEvents is for DB-state changes; this file is for UI-only signals
|
|
// like "user clicked send-to-terminal on selected text").
|
|
//
|
|
// 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.
|
|
|
|
type Listener<T> = (payload: T) => void;
|
|
|
|
interface EventBus<T> {
|
|
emit(payload: T): void;
|
|
subscribe(listener: Listener<T>): () => void;
|
|
}
|
|
|
|
function createEvent<T>(): EventBus<T> {
|
|
const listeners = new Set<Listener<T>>();
|
|
return {
|
|
emit(payload) {
|
|
for (const l of listeners) {
|
|
try {
|
|
l(payload);
|
|
} catch {
|
|
/* one bad listener shouldn't break others */
|
|
}
|
|
}
|
|
},
|
|
subscribe(listener) {
|
|
listeners.add(listener);
|
|
return () => {
|
|
listeners.delete(listener);
|
|
};
|
|
},
|
|
};
|
|
}
|
|
|
|
export interface SendToTerminalPayload {
|
|
pane_id: string;
|
|
text: string;
|
|
}
|
|
|
|
export const sendToTerminal = createEvent<SendToTerminalPayload>();
|
|
|
|
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;
|
|
}
|
|
|
|
const terminalRegistry = new Map<string, TerminalRegistration>();
|
|
const registryListeners = new Set<Listener<void>>();
|
|
|
|
function notifyRegistry(): void {
|
|
for (const l of registryListeners) {
|
|
try {
|
|
l();
|
|
} catch {
|
|
/* ignore */
|
|
}
|
|
}
|
|
}
|
|
|
|
export const terminalsRegistry = {
|
|
register(paneId: string, label: string, focus: () => void): () => void {
|
|
terminalRegistry.set(paneId, { paneId, label, focus });
|
|
notifyRegistry();
|
|
return () => {
|
|
terminalRegistry.delete(paneId);
|
|
notifyRegistry();
|
|
};
|
|
},
|
|
list(): TerminalRegistration[] {
|
|
return Array.from(terminalRegistry.values());
|
|
},
|
|
get(paneId: string): TerminalRegistration | undefined {
|
|
return terminalRegistry.get(paneId);
|
|
},
|
|
subscribe(listener: Listener<void>): () => void {
|
|
registryListeners.add(listener);
|
|
return () => {
|
|
registryListeners.delete(listener);
|
|
};
|
|
},
|
|
};
|