In the previous chapter, Authentication & Security (The "Keycard"), we secured our connection using OAuth tokens and trusted device checks.
At this point, our "Bridge" is fully functional. It has a Brain (Core), a Container (Session), a Pipe (Transport), a Dispatcher (Routing), and a Keycard (Security).
But there is one problem: The user is blind.
Imagine driving a car with a powerful engine but no dashboard. You don't know your speed, you don't know if you have gas, and you don't know if the engine check light is on.
This chapter covers the Bridge UI, or "The Dashboard." It is the layer that translates complex internal states into simple text, spinners, and QR codes that humans can understand.
Terminal applications have a unique challenge.
If we print "Connecting..." as a log, our screen will be filled with thousands of "Connecting..." lines. We need a way to draw a status line at the bottom of the screen that updates in place, while keeping the history above it safe.
The Goal: A "Head-Up Display" that shows the connection status, a login QR code, and current activity, without cluttering the terminal history.
Instead of just using console.log, we create a specialized Bridge Logger. This object holds the "State" of the UI. It knows if we are idle, working, or failing.
This is the bottom-most part of the terminal output. It is "ephemeral." When the status changes (from "Connecting" to "Connected"), the UI erases the old line and writes the new one.
To make logging in easy, the Dashboard generates a QR code directly in the terminal using ASCII block characters. This allows a user to scan their computer screen with their phone to instantly connect.
To ensure the Dashboard displays errors correctly (like "Reconnecting in 5s..."), we have a hidden debug module that lets developers simulate crashes.
Let's walk through what the user sees when they start the app.
claude bridge.โ Connecting...โ Connected.[12:00] Reading file...How do we update the bottom of the screen without erasing the top? We use special terminal codes (ANSI escape codes) to move the cursor up and clear lines.
Let's look at bridgeUI.ts to see how this magic trick is performed.
The heart of the dashboard is renderStatusLine. It handles the "erase and redraw" logic.
// bridgeUI.ts
// 1. We track how many lines the status currently takes up
let statusLineCount = 0;
function renderStatusLine() {
// 2. Erase the old status
clearStatusLines();
// 3. Draw the QR code (if visible)
if (qrVisible) {
qrLines.forEach(line => write(`${line}\n`));
}
// 4. Draw the status text (e.g., "Ready" or "Connecting")
const color = currentState === 'idle' ? chalk.green : chalk.yellow;
write(`${color(indicator)} ${currentStateText}\n`);
}
Explanation:
clearStatusLines() uses ANSI codes to move the cursor up by statusLineCount rows and delete everything.We want users to log in with their phones. We transform a URL into a block of text.
// bridgeUI.ts
import { toString as qrToString } from 'qrcode'
async function regenerateQr(url: string) {
// 1. Generate ASCII block string from URL
const qrString = await qrToString(url, { type: 'utf8', small: true });
// 2. Split into lines for rendering
qrLines = qrString.split('\n');
// 3. Update the screen
renderStatusLine();
}
Explanation: We use a library to convert the long session URL (which contains the encryption keys) into a visual pattern. This runs asynchronously, so the UI can stay responsive.
When we want to save a message to history, we must be careful not to overwrite the status line.
// bridgeUI.ts
function printLog(line: string) {
// 1. Remove the temporary status line
clearStatusLines();
// 2. Print the permanent log
write(line);
// 3. The status line will be redrawn later by the render loop
// or we can force a redraw immediately if needed.
}
Explanation: This is the key to the "Dashboard" effect. The status line is temporary; it gets out of the way whenever a real log needs to be printed, then reappears at the bottom.
How do we know the UI will correctly show "Reconnecting in 5s..." if the network fails? We can't easily unplug our internet at the exact right millisecond during testing.
We use Fault Injection. This is found in bridgeDebug.ts.
// bridgeDebug.ts
// Queue a fake error to happen next time we poll
export function injectBridgeFault(fault) {
faultQueue.push(fault);
}
// Wrap the real API with a saboteur
export function wrapApiForFaultInjection(api) {
return {
...api, // Copy real methods
// Hijack the poll method
async pollForWork(...) {
// Check if we should crash
if (shouldCrash()) throw new Error("Fake Network Error");
// Otherwise, work normally
return api.pollForWork(...);
}
};
}
Explanation:
This allows developers to polish the error-handling UI without needing to cause real network outages.
The Dashboard is smart enough to handle multiple sessions at once (e.g., if you have three terminal tabs open).
// bridgeUI.ts
if (sessionMax > 1) {
// Loop through all active sessions
for (const [id, info] of sessionDisplayInfo) {
// Format the title (e.g., "Fixing Bug #123")
const title = truncatePrompt(info.title, 35);
// Print a bullet point for this session
writeStatus(` ${title} - ${info.activity}\n`);
}
}
Explanation: The renderStatusLine function checks if we are in "Multi-Session Mode." If so, instead of just printing one status, it loops through a Map of all active sessions and prints a list.
The Bridge UI is the face of the operation. It manages the delicate balance between showing permanent history (Logs) and current state (Status).
It uses:
Congratulations! You have completed the Bridge tutorial series.
You now understand the full architecture of a modern remote terminal bridge:
You are now ready to explore the codebase, fix bugs, or add new features to the Bridge!
Generated by Code IQ