In the previous chapter, Dynamic Configuration (Feature Flags), we gave you a "Remote Control" to turn features on and off for everyone.
But we still have a major problem: Amnesia.
Imagine you meet someone, introduce yourself, and they say, "Nice to meet you!" The next day, you meet them again, and they say, "Nice to meet you!" as if they've never seen you before. By the third day, this becomes very annoying.
Currently, our CLI tool has amnesia. If a user clicks "Don't ask again," and then restarts the tool, the variable in memory is lost. The tool forgets the user's choice and asks them again.
In this chapter, we will fix this by giving the application Long-Term Memory using Global User State Persistence.
Think of Global User State Persistence as a small Notebook that the application keeps in the user's pocket.
We use this notebook to store small facts like:
desktopUpsellSeenCount: "This user has seen the ad 2 times."desktopUpsellDismissed: "This user explicitly said 'No'."We interact with this notebook using two simple functions.
getGlobalConfig)This function opens the notebook and reads the current status.
import { getGlobalConfig } from '../../utils/config.js';
// Read the entire notebook
const config = getGlobalConfig();
// Check specific facts
console.log(config.desktopUpsellSeenCount); // Output: 2
console.log(config.desktopUpsellDismissed); // Output: false
What happens: The code looks for a specific JSON file on your hard drive, parses it, and returns a JavaScript object.
saveGlobalConfig)This function writes a new fact into the notebook.
import { saveGlobalConfig } from '../../utils/config.js';
// Update the notebook
saveGlobalConfig((currentConfig) => {
return {
...currentConfig, // Keep all old notes
desktopUpsellDismissed: true // Add/Update this specific note
};
});
Key Concept: The Updater Function
Notice we don't just say save(true). We pass a function.
currentConfig (what's currently in the notebook)....currentConfig (so we don't erase other important data).Let's look at how we use this in our Upsell Dialog to be polite.
Every time the component mounts (appears on screen), we want to make a tally mark in our notebook.
// Inside DesktopUpsellStartup.tsx
useEffect(() => {
// 1. Read current count (default to 0 if missing)
const currentCount = getGlobalConfig().desktopUpsellSeenCount ?? 0;
const newCount = currentCount + 1;
// 2. Save the new count
saveGlobalConfig(prev => ({
...prev,
desktopUpsellSeenCount: newCount
}));
}, []);
Explanation:
useEffect runs once when the dialog appears.If the user selects "Never" in the menu, we need to respect that forever.
const handleSelect = (value) => {
if (value === 'never') {
// Write "True" to the dismissed flag
saveGlobalConfig(prev => ({
...prev,
desktopUpsellDismissed: true
}));
// Close the dialog
onDone();
}
};
Explanation:
Once desktopUpsellDismissed is saved as true, the check we wrote in Chapter 2 (if (config.desktopUpsellDismissed) return false;) will permanently block this dialog from showing again.
How does a JavaScript object persist after the computer is turned off? It utilizes the File System.
Here is what happens when you call saveGlobalConfig:
While the real code handles errors and file locking, here is the simplified version of what is happening inside utils/config.js.
import fs from 'fs'; // Node.js File System module
const CONFIG_PATH = '~/.tengu/config.json';
export function getGlobalConfig() {
// Read file from disk
const rawData = fs.readFileSync(CONFIG_PATH, 'utf-8');
// Convert text to Object
return JSON.parse(rawData);
}
export function saveGlobalConfig(updater) {
// 1. Get current
const current = getGlobalConfig();
// 2. Calculate new
const newData = updater(current);
// 3. Save to disk
fs.writeFileSync(CONFIG_PATH, JSON.stringify(newData));
}
Why is this cool? By using a simple JSON file, the user can actually open this file in a text editor and see exactly what data we are storing about them. This builds trust and transparency.
In this chapter, we solved the "Amnesia" problem.
getGlobalConfig to read the user's history.saveGlobalConfig to remember the user's choices ("Don't ask again").Now our application is:
The final piece of the puzzle is Feedback. We know we are showing the dialog, but do we know if users are actually clicking "Try"? Are they clicking "Not Now"?
In the final chapter, we will learn how to send anonymous signals back to our servers to measure success.
Next Chapter: Analytics & Telemetry
Generated by Code IQ