feat: port CLI, TUI, and launcher from v1.5 into v2 (#1452)

Ports the CLI, TUI, and launcher from v1.5 into v2 behind a single
`mcp-inspector` entrypoint (launcher → web/cli/tui). Includes the
`mcp-inspector --web --dev` launcher fix (Vite alias resolution + prod-server
double-start), the root `postinstall` install cascade with simplified CI, and
spec/AGENTS docs (no-workspaces rationale, Development setup).

Closes #1246.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Bob Dickinson 2026-06-14 17:27:49 -07:00 • committed by GitHub
commit 2488ca5e38
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -0,0 +1,61 @@
import { useState, useEffect, useCallback } from "react";
function clampFirstVisible(
first: number,
selected: number,
visibleCount: number,
): number {
if (selected < first) return selected;
if (selected >= first + visibleCount) return selected - visibleCount + 1;
return first;
}
export interface UseSelectableListOptions {
/** When this reference changes, reset selection to 0 (e.g. tools list when switching servers) */
resetWhen?: unknown;
}
/**
* Manages selection and scroll position for a virtualized list.
* Returns selection state and a setSelection that updates both
* selectedIndex and firstVisible so the selected item stays in view.
*/
export function useSelectableList(
itemCount: number,
visibleCount: number,
options?: UseSelectableListOptions,
) {
const [selectedIndex, setSelectedIndex] = useState(0);
const [firstVisible, setFirstVisible] = useState(0);
const setSelection = useCallback(
(newIndex: number) => {
setSelectedIndex(newIndex);
setFirstVisible((prev) =>
clampFirstVisible(prev, newIndex, visibleCount),
);
},
[visibleCount],
);
// Reset when the list reference changes (e.g. different server).
useEffect(() => {
if (options?.resetWhen !== undefined) {
setSelectedIndex(0);
setFirstVisible(0);
}
}, [options?.resetWhen]);
// Clamp when list shrinks
useEffect(() => {
if (itemCount > 0 && selectedIndex >= itemCount) {
const newIndex = itemCount - 1;
setSelectedIndex(newIndex);
setFirstVisible((prev) =>
clampFirstVisible(prev, newIndex, visibleCount),
);
}
}, [itemCount, selectedIndex, visibleCount]);
return { selectedIndex, firstVisible, setSelection };
}