Welcome to the final chapter of the Bridge tutorial!
In the previous chapter, Global State Integration, we successfully decoupled the UI from the Logic. We learned how to flip a switch in the global state to start the "Furnace" (the WebSocket connection) in the background.
But now we have a new situation.
Imagine the user has successfully connected. The bridge is running. Hours pass. The user types /remote-control again.
What should happen?
We need a menu that allows the user to:
This chapter covers the Interactive Session Dialog, built using the BridgeDisconnectDialog component.
Terminal interfaces are unique. You can't usually "click" buttons with a mouse. We have to build our own navigation system using the keyboard.
We will focus on three main mechanics:
First, let's look at what we are rendering. We use a component library designed for the terminal (Ink). We render a list of items, but only one item looks "focused" (highlighted) at a time.
We use a simple integer state variable, focusIndex, to track where the user is looking.
// From file: bridge.tsx
function BridgeDisconnectDialog({ onDone }) {
// 0 = Disconnect, 1 = QR Code, 2 = Continue
const [focusIndex, setFocusIndex] = useState(2);
// Check if "Disconnect" is the focused item
const isDisconnectFocused = focusIndex === 0;
// Render the list
return (
<Box flexDirection="column">
<ListItem isFocused={isDisconnectFocused}>
<Text>Disconnect this session</Text>
</ListItem>
{/* ... other list items ... */}
</Box>
);
}
Explanation:
focusIndex: A number (0, 1, or 2).isFocused: A prop we pass to ListItem. If true, the component renders a visual indicator (like a color change or a > arrow) to show it is selected.
Since there is no mouse, we need to listen for keystrokes. We use a custom hook called useKeybindings.
This hook maps specific "Intentions" (like "select:next") to specific JavaScript functions.
// From file: bridge.tsx
// Define the number of items in our menu
const ITEM_COUNT = 3;
useKeybindings(
{
// When the user presses the Down Arrow or Tab
'select:next': () => {
setFocusIndex(i => (i + 1) % ITEM_COUNT)
},
// When the user presses the Up Arrow
'select:previous': () => {
setFocusIndex(i => (i - 1 + ITEM_COUNT) % ITEM_COUNT)
},
// ... handling Enter key below
}
);
Explanation:
%): This math trick creates a "loop."3 % 3 = 0. The cursor loops back to the top!useKeybindings: This hook intercepts user input so it doesn't just print characters to the screen.
When the user presses Enter, we check focusIndex to see which button they "pressed."
// Inside useKeybindings map:
'select:accept': () => {
if (focusIndex === 0) {
handleDisconnect(); // User selected "Disconnect"
} else if (focusIndex === 1) {
handleShowQR(); // User selected "Show QR"
} else {
handleContinue(); // User selected "Continue"
}
}
Explanation:
onClick handler in web development.If the user chooses to disconnect, we simply reverse the logic we learned in Chapter 4: Global State Integration.
Instead of turning the global switch ON, we turn it OFF.
// From file: bridge.tsx
function handleDisconnect() {
setAppState(prev => ({
...prev,
replBridgeEnabled: false, // <--- The Kill Switch
replBridgeExplicit: false
}));
// Tell the CLI command lifecycle we are finished
onDone("Remote Control disconnected.");
}
Explanation:
replBridgeEnabled: false: The moment this state updates, the background logic (the "Furnace") detects the change and immediately cuts the WebSocket connection.onDone: This closes the interactive dialog UI and returns the user to the standard command prompt.Let's visualize the "Game Loop" of this interactive dialog.
One of the options is to show a QR code. This is useful if the user closed the terminal window on their phone and needs to scan again.
We generate this efficiently using a useEffect. We don't want to regenerate the heavy QR pixel data every single millisecond, only when requested.
// From file: bridge.tsx
const [showQR, setShowQR] = useState(false);
const [qrText, setQrText] = useState('');
useEffect(() => {
if (showQR && displayUrl) {
// Convert the URL string into a QR block text
qrToString(displayUrl, { type: 'utf8' })
.then(generatedQR => setQrText(generatedQR));
}
}, [showQR, displayUrl]);
Explanation:
qrToString to turn a URL (like https://...) into a block of text that looks like a barcode.qrText) and render it inside a <Box> if showQR is true.Congratulations! You have navigated the entire architecture of a complex CLI feature.
Let's review the journey of the Bridge:
/remote-control.You now understand how to build a feature that is lazy-loaded, secure, state-driven, and interactive. These patterns form the backbone of high-quality CLI applications.
Project Complete.
Generated by Code IQ