In the previous chapter, Configuration Priority System, we learned how to decide which setting wins when there are conflicts (like Environment Variables vs. User Input). We figured out what the value should be.
Now, we need to make sure that value actually sticks.
Computers have two types of memory:
When a user types /effort high, two things must happen:
The State and Persistence Bridge is the code that synchronizes these two worlds. It ensures your "Current Session" and your "Saved Settings" stay in sync.
The user successfully sets the effort level:
/effort high
We need to:
settings.json file (Persistence).To achieve this, we use two specific tools provided by the framework.
updateSettingsForSource)This is the "Long-term Memory." It writes data to the user's configuration file. This happens inside our logic controller.
useAppState)This is the "Short-term Memory." It is a React Hook that holds the current variables in memory. This happens inside our UI component.
We handle this synchronization in two different parts of our code: the Logic Function (Disk) and the React Component (RAM).
Inside our logic function setEffortValue (which we looked at in Chapter 3), we save the data.
// effort.tsx
import { updateSettingsForSource } from '../../utils/settings/settings.js';
// Inside setEffortValue()...
const persistable = toPersistableEffort(effortValue);
if (persistable !== undefined) {
// This writes to the physical file on the user's disk
updateSettingsForSource('userSettings', {
effortLevel: persistable
});
}
updateSettingsForSource opens the user's settings file, finds the effortLevel key, and updates it. This ensures that next time the app loads, it reads this value.
Saving to disk doesn't automatically tell the running React app that something changed. We need to manually update the "Live State." We do this in the ApplyEffortAndClose component.
First, we grab the "setter" hook:
// effort.tsx
import { useSetAppState } from '../../state/AppState.js';
function ApplyEffortAndClose({ result, onDone }) {
// This hook allows us to write to the live application memory
const setAppState = useSetAppState();
// ... logic continues
useSetAppState gives us a function (similar to React's setState) that updates the global store.
Now we use useEffect to trigger the update. We take the result calculated by our logic and feed it into the state.
// ... inside ApplyEffortAndClose
const { effortUpdate } = result;
React.useEffect(() => {
if (effortUpdate) {
// Update the global variable 'effortValue'
setAppState(prev => ({
...prev,
effortValue: effortUpdate.value
}));
}
// ... cleanup code
}, [effortUpdate, setAppState]);
1. We check if effortUpdate exists (it comes from the logic function).
2. We call setAppState.
3. We use ...prev to keep all other state variables (like model name, history, etc.) unchanged, only updating effortValue.
Let's visualize how the data travels from your keyboard to the disk and back to the memory.
So far we have written the state. How do we read it to show the user?
We use useAppState. This hook listens to the RAM. If the RAM changes (because of what we did in Part 2), this hook triggers a re-render.
// effort.tsx
function ShowCurrentEffort({ onDone }) {
// Select ONLY the effortValue from the huge state object
const effortValue = useAppState(s => s.effortValue);
// Pass this value to our helper to generate the message
const { message } = showCurrentEffort(effortValue, model);
onDone(message);
return null;
}
useAppState(s => s.effortValue) means "Watch the global state, but only wake me up if effortValue changes."
You might ask: Why doesn't updateSettingsForSource just automatically update useAppState?
Decoupling (separating) them is safer:
Congratulations! You have completed the Effort project tutorial series.
You have built a fully functional, professional-grade CLI feature that:
You now understand the core architecture of building complex, interactive tools where user preferences must be both immediate and permanent.
Generated by Code IQ