Welcome back! In the previous chapter, Modal & Portal Layouts, we learned how to create floating windows and give them "spatial awareness." We now have a nice UI structure.
However, we have a new problem: Communication.
Imagine you have a "Stop Button" inside a floating Modal at the bottom of the screen. You also have an "AI Text Generator" running in the main window at the top.
When the user clicks "Stop," the Generator needs to halt.
In a standard React app, you might try to pass a function like onStop from the top component down through 10 layers of parents to reach the button. This is called "Prop Drilling," and it makes your code a nightmare to maintain.
This chapter introduces Message Handling & Queues to solve this:
Think of the Mailbox as a central hub or a "Walkie-Talkie" channel.
Component A and Component B don't need to know each other exists. They just need access to the Mailbox.
We provide the Mailbox to the entire app using the MailboxProvider.
Let's build that "Stop Button." It uses the useMailbox hook to get the walkie-talkie.
import { useMailbox } from './mailbox';
function StopButton() {
const mailbox = useMailbox();
return (
<Button onPress={() => {
// Broadcast the signal!
mailbox.emit('abort-generation');
}}>
STOP
</Button>
);
}
Now, somewhere completely different in the app, the Generator listens for that signal.
import { useMailbox } from './mailbox';
function Generator() {
const mailbox = useMailbox();
useEffect(() => {
// Define what happens when we hear the signal
const onAbort = () => console.log("Stopping process...");
// Start listening
mailbox.on('abort-generation', onAbort);
// Cleanup: Stop listening when we unmount
return () => mailbox.off('abort-generation', onAbort);
}, [mailbox]);
return <Text>Generating...</Text>;
}
Why is this better?
You can move the StopButton anywhereβinto a sidebar, a modal, or a popupβand it will still work without changing a single line of code in the Generator.
QueuedMessageContext)When building a TUI (Terminal User Interface) for AI, messages often stream in chunks. We need to distinguish between:
If we don't manage this, the text looks like a wall of unreadable blocks. QueuedMessageContext helps us apply the correct "Indent" and "Padding" automatically.
The context calculates how much Padding a message box should have based on two factors:
isFirst: Is this the start of a new message block?useBriefLayout: Is the user requesting a compact view?Here is a component that wraps our text. It automatically knows if it should add padding or stay compact.
import { QueuedMessageProvider, useQueuedMessage } from './QueuedMessageContext';
function MessageBlock({ isFirst, children }) {
// 1. Wrap content in the Provider
return (
<QueuedMessageProvider isFirst={isFirst}>
<MessageContent>{children}</MessageContent>
</QueuedMessageProvider>
);
}
Inside MessageContent, we can read the calculated values to style our text.
function MessageContent({ children }) {
// 2. Consume the calculated layout values
const { paddingWidth, isFirst } = useQueuedMessage();
return (
<Box>
{/* Show an arrow only for the first line */}
{isFirst && <Text color="blue">β </Text>}
{/* Indent content dynamically */}
<Box paddingLeft={paddingWidth}>
{children}
</Box>
</Box>
);
}
Result:
useBriefLayout is false: You get nice, readable indentation (paddingWidth = 4).useBriefLayout is true: The padding collapses to 0 for a compact view.Let's look under the hood to see how the signal flows through the system.
Open mailbox.tsx. It is a wrapper around a utility class.
// mailbox.tsx (Simplified)
export function MailboxProvider({ children }) {
// 1. Create a stable instance of the Mailbox class
const mailbox = useMemo(() => new Mailbox(), []);
// 2. Pass it down to the whole app
return (
<MailboxContext.Provider value={mailbox}>
{children}
</MailboxContext.Provider>
);
}
The Mailbox class itself (imported from utils) is simply an "Event Emitter"βa standard pattern in programming for handling lists of listeners.
Open QueuedMessageContext.tsx. This file does the math for visual styling.
// QueuedMessageContext.tsx (Simplified)
const PADDING_X = 2; // Default indentation
export function QueuedMessageProvider({ isFirst, useBriefLayout, children }) {
// 1. Calculate padding. If "Brief", use 0. Else use 2.
const padding = useBriefLayout ? 0 : PADDING_X;
// 2. Prepare the context value
const value = {
isQueued: true,
isFirst,
paddingWidth: padding * 2 // e.g., 4 spaces
};
// 3. Render the Box with the calculated padding applied immediately
return (
<QueuedMessageContext.Provider value={value}>
<Box paddingX={padding}>{children}</Box>
</QueuedMessageContext.Provider>
);
}
Key Takeaway: By doing the math (padding * 2) inside the Provider, none of the child components need to know about "Brief Layouts" or math. They just ask: "How much padding should I use?" and the Context gives them the final number.
In this chapter, we learned how to manage communication and flow:
Now that our components can talk to each other and look good doing it, we need to manage the most complex part of our app: Voice & Audio.
Next Chapter: Voice State Manager
Generated by Code IQ