Welcome back! In the previous chapter, Voice State Manager, we built a high-speed sidecar to handle rapid audio updates.
Now that our app can listen and process data, it needs to talk back. But there is a risk. If the app talks too muchβor too fastβit becomes annoying.
Imagine a waiter who updates you on every micro-action:
In a Terminal UI, this looks like a wall of text scrolling by:
Loading 10%...
Loading 20%...
Loading 30%...
Success!
This buries important information (like errors) and clutters the screen.
This chapter introduces the Notification Pipeline. It acts like a smart Traffic Controller. It knows that "Fire Alarm" is more important than "Radio Music," and it knows how to update a "Loading" message in place instead of spamming new lines.
The pipeline manages three things that a standard console.log cannot:
We use the useNotifications hook to access the pipeline.
import { useNotifications } from './notifications';
function SaveButton() {
const { addNotification } = useNotifications();
return (
<Button onPress={() => {
addNotification({
key: 'save-success', // Unique ID
text: 'File saved successfully!',
priority: 'medium',
color: 'green'
});
}}>
Save
</Button>
);
}
What happens? A green message appears. If another message is currently showing, this one enters a Queue and waits for the current one to finish (timeout).
Folding solves the "Spammy Waiter" problem. It allows a new notification to absorb an old one if they share the same key.
key: 'download', text: "10%"key: 'download', text: "20%"Here is how we create a notification that updates itself.
function Downloader() {
const { addNotification } = useNotifications();
const updateProgress = (percent) => {
addNotification({
key: 'download-job', // Same key every time!
text: `Downloading... ${percent}%`,
priority: 'medium',
// The Fold Logic: Replace the old one with the new one
fold: (prev, next) => next
});
};
// ... call updateProgress(10), updateProgress(20), etc.
}
Result:
The user sees a single notification box that animates: Downloading... 10% -> Downloading... 20%. No scrolling spam!
Sometimes, you need to cut the line. If the internet connection dies, you don't want the error message to wait behind "File Saved."
We use priority: 'immediate' for this.
addNotification({
key: 'network-error',
text: 'CONNECTION LOST!',
priority: 'immediate', // Skips the queue!
color: 'red',
timeoutMs: 10000 // Stay longer (10s)
});
What happens?
Let's look at how the pipeline makes decisions.
Open notifications.tsx. The core logic lives in addNotification.
First, the function checks if the new message is an emergency.
// notifications.tsx (Simplified)
const addNotification = (notif) => {
if (notif.priority === 'immediate') {
// 1. Clear current timeout (stop auto-dismiss)
clearTimeout(currentTimeoutId);
// 2. Force update state immediately
setAppState(prev => ({
...prev,
notifications: {
current: notif, // Hijack the screen
// Put the old 'current' back into the queue
queue: [prev.current, ...prev.queue]
}
}));
return;
}
// ... continue to normal logic
};
If it's not immediate, we check if we can merge it.
// notifications.tsx (Simplified)
// Inside setAppState...
if (notif.fold) {
// Check if the CURRENT message has the same key
if (prev.current?.key === notif.key) {
// Run the user's fold function (usually returns 'next')
const folded = notif.fold(prev.current, notif);
return {
...prev,
notifications: {
current: folded, // Update in place!
queue: prev.queue
}
};
}
}
We need a way to remove messages after timeoutMs. We use a recursive timeout pattern.
// notifications.tsx (Simplified)
const processQueue = useCallback(() => {
setAppState(prev => {
const next = getNext(prev.queue); // Get highest priority item
// Set a timer to clear THIS notification
setTimeout(() => {
// When time is up, clear current and run processQueue again
processQueue();
}, next.timeoutMs);
return {
notifications: { current: next, queue: remaining }
};
});
}, []);
This ensures that the pipeline never gets stuck. As soon as one message finishes, it wakes up and pulls the next one from the queue.
In this chapter, we built a polite and efficient communication system:
Our application is now fully interactive. It has visuals, layers, voice input, and a smart notification system.
But how do we know if it's actually working well? Are users hitting errors we don't see? In the final chapter, we will build a system to track the health of our app.
Next Chapter: Telemetry & Stats Store
Generated by Code IQ