Welcome to the final chapter of our DesktopUpsell tutorial!
In the previous chapter, Global User State Persistence, we gave our application a "long-term memory" so it remembers if a user asked us not to bother them again.
However, we have one final blind spot. Imagine you are a store manager. You set up a beautiful display for a new product. Then, you put on a blindfold and earplugs.
Without Analytics, you are developing blindfolded. You don't know if your feature is a success or a failure.
In this chapter, we will build the application's "Nervous System"βa way for the app to send signals back to us about how it is being used.
Telemetry is the automatic recording and transmission of data from real-world use.
We don't want to spy on the user's personal files. We just want to answer specific questions about our feature, such as:
We achieve this using a function called logEvent.
Using analytics is surprisingly simple. It usually involves sending a "Message" with a "Name" and some "Details."
We want to know every time the dialog is shown to a user. We add this right where we increment the view count (from the previous chapter).
import { logEvent } from '../../services/analytics/index.js';
// Inside our useEffect hook
logEvent("tengu_desktop_upsell_shown", {
seen_count: newCount
});
Input:
"tengu_desktop_upsell_shown" (This is the unique ID we look for in our dashboards).{ seen_count: 2 }.Output: This sends a signal to our server saying: "Hey! Someone just saw the dialog. By the way, this is the 2nd time they've seen it."
Knowing people saw the dialog is good, but knowing they engaged with it is better.
const handleSelect = (value) => {
if (value === 'try') {
// 1. Log the success
logEvent("tengu_desktop_upsell_accepted");
// 2. Perform the action
setShowHandoff(true);
}
};
Explanation: When we look at our data later, we can do simple math:
What happens when you call logEvent? Does it slow down the app?
Think of logEvent like dropping a postcard in a mailbox.
This is called Fire-and-Forget. The application does not pause to wait for the server to say "Okay, got it." This ensures the user interface remains snappy.
Here is the journey of a single analytics event:
While the real implementation handles batching (sending multiple events at once to save battery), here is a simplified version of what logEvent does internally.
export function logEvent(eventName: string, props: object) {
// 1. Combine user data with system context
const payload = {
event: eventName,
properties: props,
// Automatically add useful context
context: {
os: process.platform, // e.g., 'darwin' (macOS)
timestamp: Date.now()
}
};
// 2. Send to backend (Fire and Forget)
// We don't use 'await' here because we don't want to block UI
sendToBackend(payload).catch(err => {
// If analytics fail, suppress the error.
// Never crash the app just because tracking failed.
console.error('Analytics failed silently');
});
}
Why the "Context" matters:
By automatically adding os, we can later answer questions like: "Do Windows users click 'Try' more often than macOS users?" without having to manually pass that data every time.
Congratulations! You have completed the DesktopUpsell system. Let's review the architecture we have built over these five chapters.
We used Ink to render a beautiful, interactive React-based UI inside the terminal.
We built a "Bouncer" that checks Hardware compatibility before showing the UI.
We added Feature Flags to turn the feature on/off remotely without code changes.
We added Persistence to remember user choices and prevent nagging.
We added Telemetry to measure success and learn from user behavior.
You have gone from rendering simple text to building a robust, production-grade feature with targeting, safety rails, and feedback loops.
These five pillarsβRendering, Logic, Configuration, Persistence, and Telemetryβare the building blocks of almost every modern software feature you will encounter.
Happy Coding!
Generated by Code IQ