The Hidden Architecture: State, Commands, and Durable Behavior
In yczz/oc-english, a screen can make a promise only if the application has somewhere reliable to keep the result. This chapter follows that promise through the project’s local architecture: from profile creation, through learning and customization mutations, to logout and relogin. It then separates what the repository’s tests and screenshots are designed to establish from what they cannot establish. The practical payoff is a way to read the application by responsibility—who owns a decision, what crosses the boundary, and what survives afterward.
The architecture is not interesting because it uses several technologies. It is interesting because the learning-to-customization loop depends on their division of labor. JavaScript presents an action and renders a response. Tauri carries the action into Rust. Rust validates and mutates the profile. The profile is serialized to a local file. The interface then receives enough information to show the changed state. If one of those boundaries is unclear, a point, practiced word, or wardrobe item can appear to exist without actually belonging to the learner’s saved world.
One profile from creation to return
The application begins with a local profile rather than a remote account. Registration creates a profile with a name, 200 points, a small starter inventory, an empty character list, no custom items, and no unit progress. The profile is saved under the application’s data directory, and the current process keeps a copy of it as the active profile. Logging in later loads the named JSON profile; logging out clears the in-memory current profile without deleting the saved file.
The distinction between active and saved state is important. The active profile is what commands can mutate now. The JSON file is what makes that state available again later. A learner can create a character, alter a face, buy an item, complete practice, pass an exam, and then return to the registration screen. When they log in again, the application can recover the profile’s points, characters, inventory, custom items, and unit progress because those changes were serialized along the way.
The profile lifecycle has clear entry and exit conditions. Registration is allowed only for a valid, unused name; it creates and saves a new profile, then makes that profile active. Login loads an existing named file and replaces the current in-memory profile. Logout clears the active profile, so later commands that require a profile cannot silently act on whoever logged in before. The saved file remains available for the next explicit login. This is a small lifecycle, but it gives the product a predictable answer to “whose points and character am I changing?”
The same pattern applies inside a session. The active-character identifier selects which character the frontend displays, but it does not move the profile’s points or learning progress to another file. Buying an item changes shared inventory; equipping it changes the selected character. Practice changes progress keyed to a book and unit; the room changes the selected character’s placements. These are separate transitions that happen to be saved through the same profile boundary. The application does not need to infer ownership from the screen currently open.
Validation also determines whether a transition reaches the save step. An operation that refers to an unknown character, item, unit, or invalid slot is rejected by the command rather than written as a partial state. A successful operation returns an updated profile or a result, and the frontend uses that response to refresh the relevant view.