fix: skip polling with empty session ID, use correct XDA parser API

- Add `enabled` option to useSession hook; OrchestratorScreen passes
  enabled: !!orchestratorId to avoid polling /api/sessions/ with an
  empty string when no orchestrator is running
- Replace term.onData XDA handler with term.parser.registerCsiHandler
  matching the web dashboard implementation — onData only fires for
  outgoing user input, not incoming WebSocket data where the CSI > q
  query actually arrives

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
suraj-markup 2026-03-03 05:07:27 +05:30
parent 0773137a65
commit d6d5dd6642
3 changed files with 22 additions and 8 deletions

View File

@ -5,6 +5,11 @@ import type { DashboardSession } from "../types";
const POLL_INTERVAL = 5_000;
interface UseSessionOptions {
/** Set to false to disable polling. Defaults to true. */
enabled?: boolean;
}
interface UseSessionResult {
session: DashboardSession | null;
loading: boolean;
@ -12,7 +17,8 @@ interface UseSessionResult {
refresh: () => void;
}
export function useSession(id: string): UseSessionResult {
export function useSession(id: string, options?: UseSessionOptions): UseSessionResult {
const enabled = options?.enabled ?? true;
const { fetchSession } = useBackend();
const [session, setSession] = useState<DashboardSession | null>(null);
const [loading, setLoading] = useState(true);
@ -51,6 +57,12 @@ export function useSession(id: string): UseSessionResult {
}, []);
useEffect(() => {
if (!enabled) {
setSession(null);
setLoading(false);
return;
}
startPolling();
const handleAppState = (nextState: AppStateStatus) => {
@ -69,12 +81,13 @@ export function useSession(id: string): UseSessionResult {
stopPolling();
sub.remove();
};
}, [startPolling, stopPolling]);
}, [enabled, startPolling, stopPolling]);
const refresh = useCallback(() => {
if (!enabled) return;
setLoading(true);
doFetch();
}, [doFetch]);
}, [enabled, doFetch]);
return { session, loading, error, refresh };
}

View File

@ -36,7 +36,7 @@ function getZoneCounts(sessions: DashboardSession[]) {
export default function OrchestratorScreen({ navigation }: Props) {
const { sessions, orchestratorId, loading, error, refresh } = useSessions();
const { sendMessage, terminalWsUrl } = useBackend();
const { session: orchSession } = useSession(orchestratorId ?? "");
const { session: orchSession } = useSession(orchestratorId ?? "", { enabled: !!orchestratorId });
const [message, setMessage] = useState("");
const [sending, setSending] = useState(false);

View File

@ -148,10 +148,11 @@ export const TERMINAL_HTML = `<!DOCTYPE html>
});
// XDA handler: respond to CSI > q with XTerm identity (enables tmux clipboard)
term.onData(function (data) {
if (data === '\\x1b[>q') {
term.write('\\x1bP>|XTerm(370)\\x1b\\\\');
}
// Must use parser.registerCsiHandler — onData only captures outgoing user input,
// not incoming data from the WebSocket. The XDA query arrives via ws → term.write().
term.parser.registerCsiHandler({ prefix: '>', final: 'q' }, function () {
term.write('\\x1bP>|XTerm(370)\\x1b\\\\');
return true;
});
document.addEventListener('message', function (evt) { handleRNMessage(evt.data); });