ConnectionsGENERALSTAGE-SPECIFICILLUSTRATIVE

Problem Solving and Frontend

User Goal → Interaction → State → UI. The screen is the last step. What the user is trying to do, the interactions that get them there, and the state each interaction reads and changes come first — and they decide the UI far better than a component library does.

The moveWorked exampleNext questions

The situation, the reflex, and why it stalls

Every lesson starts where being stuck starts: someone has a problem, and the first move that comes to mind feels like progress.

The question

A screen in the store has to be built. How does the loop get from what the user wants to the state the screen holds and the interface that shows it, and what does it hand to the frontend domain?

The situation

The checkout page. You can picture it — address form, payment fields, a summary, a Pay button — and you could start laying it out now. What you cannot say is what the page knows at any moment, what happens when the Pay button is clicked twice, or what the customer sees while the server is deciding whether the last unit is still there.

The reflex

Start from the layout. Pick components, arrange them, wire the form. The page becomes visible quickly, and a visible page is a page that exists; the state will be whatever the components need, added as they need it.

Why it stalls

State accumulates where components put it. The cart total lives in the summary component, the stock warning in the item row, the "submitting" flag in the button; when the server rejects the cart, three components disagree about what happened (Who Owns This State?).

What the reflex produces — and fails to produce
  • State accumulates where components put it. The cart total lives in the summary component, the stock warning in the item row, the "submitting" flag in the button; when the server rejects the cart, three components disagree about what happened (Who Owns This State?).
  • The interactions were never enumerated, so the double click, the back button during submission and the refresh after payment are each discovered by a customer, and each is fixed in the component nearest the symptom.
  • The page shows the server's state as of page load and its own edits since, with no rule for which wins. The customer sees a price the admin changed a minute ago as the old price until they refresh, and the server charges the new one.
  • The visible page looked like progress and encoded a dozen decisions about state that nobody made, each of which will be made properly later, by rewriting a component.
ProblemUnderstandRequirementsConstraintsUnknownsDecompositionSmallest StepModelExperimentObserveDebugLearnIterate

The move

Precisely enough to apply it to a problem you have never seen — not a slogan.

  • Run the chain: User Goal → Interaction → State → UI. The goal is what the user is trying to accomplish on this screen, in one sentence. The interactions are every action they can take toward it, including the awkward ones — double click, back, refresh, tab away. The state is what the screen must know to serve those interactions: what came from the server, what the user has changed, what is in flight, what has failed. The UI is what makes the state visible and the interactions possible — and it is designed last, from the state.
  • Enumerate the interactions before the state, because the state exists to serve them. "Click Pay twice" is an interaction, and it produces a state requirement — a submission in flight that the second click can see — that no layout would have surfaced (States That Must Be Unrepresentable).
  • Sort the state by who owns it. Server state is the server's and the screen holds a copy that can be stale; client state is the user's edits; transient state is what is in flight or has failed. Each kind has a different rule for when it is trusted, and mixing them is where the disagreements between components come from (Source of Truth).
  • Hand off at the state. The frontend domain teaches state categories, server versus client state, optimistic updates, loading and error states and form handling; each is a mechanism for one row of the state list, and the list says which rows the screen has.

The chain, screen by screen

Four steps, and the frontend domain owns the mechanisms behind the last two. The first two are discovery: what the user wants and what they will do, which together specify what the screen must know.

User Goal → Interaction → State → UI
  1. 1
    User Goal

    One sentence with an observation the user can see.

    fails by The screen's name taken as its purpose.

  2. 2
    Interaction

    Every action toward the goal, plus the interruptions — double click, back, refresh, external change.

    fails by The happy-path clicks only.

  3. 3
    State

    What the screen must know per interaction, tagged server / client / transient, with a rule for conflicts.

    fails by Whatever the components happened to need.

  4. 4
    UI

    Every state value visible; every element showing a row; designed from the list.

    fails by Designed first, from a component library.

The chain on a checkout page is an hour and a page of notes. Its output is a state list that any framework can implement and that the frontend lessons on state categories and ownership take from there.

Checkout, decomposed by state

The state list for checkout as a tree, with a testable observation on every leaf. A leaf you cannot describe a test for is a state row nobody has decided the behaviour of.

What the checkout screen must know
Checkout page state
  • Server state (a copy, with an age)the truth is on the server; the screen holds a snapshot
    • Cart items and current pricestestable After the admin changes a price, the screen either shows the new price or shows that its copy is stale — never the old price silently.
    • Stock per itemtestable An item that sold out while the page was open is flagged before Pay is enabled, or Pay fails with that item named.
  • Client state (the user's edits)not yet sent; must survive a re-render and be sent exactly once
    • Edited quantitiestestable Changing a quantity updates the displayed total without a server round trip, and the server receives the edited quantity on Pay.
    • Payment fieldstestable Fields are preserved across a validation error and cleared after success.
  • Transient state (in flight, failed)the interruptions live here
    • Submission in flighttestable A second click on Pay while one is in flight does nothing visible except show that one is in flight; the server receives one request.
    • Submission resulttestable Success navigates to the order; failure shows the reason next to the thing that failed; a refresh after success shows the order, not the form.
    • Price-changed acknowledgementtestable After a detected price change, Pay is disabled until the new total is acknowledged.

Every leaf came from an interaction in the list. The tree is the specification the components implement; a component that holds state not in the tree is holding a decision nobody made.

Layout first against state first

The same checkout page built two ways. The difference is not the look; it is where the decisions about state were made and by whom.

Building the checkout page
Layout first
Components chosen and arranged; each holds what it needs — the summary computes its total, the button holds "submitting", the row holds its stock warning. The double click sends two requests; the price change is invisible until refresh; three components disagree after a rejection.
State first
Goal and interactions listed; state rows tagged and owned; the UI designed so that each row is visible. The button reads the in-flight row; the banner reads the price-changed row; a rejection updates one row and every element agrees.

State decided by the chain is decided once, with a rule for conflicts. State decided by components is decided many times, by whichever component got there first, with no rule — and the customer is the one who finds the disagreement (What Information Changes Over Time?).

How to do it

Most important first.

  • Write the user's goal for the screen as one sentence with an observation: "pay for what is in my cart and know it worked — I see an order number".
  • List every interaction, including the ones you would rather not think about: double click, back during submission, refresh after success, price change while on the page, session expiry mid-form (Edge Cases From Examples).
  • For each interaction, write what the screen must know to handle it. Collect the answers into a state list and tag each row server, client or transient.
  • For each state row, write what the user sees in each of its values — loading, ready, failed, stale. A row with a value that has no visible representation is a decision not yet made (Loading, Error, Empty — The States You Did Not Render in the frontend domain covers the mechanics).
  • Only then sketch the UI, and check every element against the state list: an element that shows no state row is decoration, and a state row no element shows is invisible to the customer.

Worked on a concrete problem

The move has to produce something. This is what it produced.

  • The checkout page, run through the chain. Goal: pay for my cart and see an order number. Interactions: edit quantity; remove an item; enter payment details; click Pay; click Pay again while it is working; press back while it is working; refresh after success; the admin changes a price while I am here. State — server: cart items, current prices, stock; client: quantities as edited, payment fields; transient: submission in flight, submission result, a price-changed flag. UI, from the state: the Pay button is disabled while a submission is in flight and shows why; a price change shows a banner with the old and new price and requires an acknowledgement before Pay; a refresh after success lands on the order page because the submission result was recorded server-side under the idempotency key. None of these came from the layout.
  • The chat app's conversation view. Goal: read new messages and reply. Interactions: scroll up for history, type, send, send while offline, receive while scrolled up. State — server: messages, members' read pointers; client: the draft; transient: pending sends, connection status, "new messages below" when scrolled up. The last transient row is the one the layout would never have produced, and it is the difference between a chat that feels alive and one that jumps.
  • The analytics dashboard. Goal: see today's revenue and compare to last week. Interactions: change the range, refresh, wait. State — server: the two numbers and their as-of time; transient: loading, stale. The chain is short and its one finding is the as-of time: a dashboard that does not show when its number was computed will be trusted when it should not be (Case: An Analytics Dashboard).

How you know it worked

What now exists that did not before, and what question you can now ask.

  • An interaction list exists for the screen and includes at least three interactions you would not have designed a layout around.
  • Every state row is tagged server, client or transient, and the rule for which wins on conflict is written.
  • Every state value has a visible representation, and every UI element shows some state row.
  • The awkward interactions — double click, back, refresh — have designed behaviour rather than discovered behaviour.

The questions you can now ask

The field this whole domain exists for. After this lesson, these are the questions to put to an unfamiliar problem.

Next questions
  • ?What is the user trying to accomplish on this screen, and what would they see that tells them it worked?
  • ?What can they do here — including double click, back, refresh and tab away — and what must the screen know to handle each?
  • ?Which state is the server's, which is the user's, which is in flight — and which wins when they disagree?
  • ?Does every state value have something the user can see, and does every visible element show a state row?

What can go wrong

How the move itself fails
  • The interaction list becomes exhaustive: every keyboard shortcut, every resize. The list is what the user does toward the goal plus the interruptions that break it; a static product page has three rows.
  • The state list is designed and then the components are given their own state anyway, "for convenience". The list is the ownership; a component that duplicates a row will disagree with it.
  • Server state is treated as truth on the client. It is a copy with an age; the chain's point is that the screen must know the age and what to do when the copy is stale.
  • The UI is postponed until the state is perfect. The state list on a checkout page takes an hour; the layout should follow the same day.
What the move costs
  • The chain produces no visible page for the first hour, and a visible page is what a stakeholder recognises as frontend work.
  • Designing the awkward interactions up front adds state — flags, banners, disabled reasons — that a demo would not need. A demo is what the reflex builds.
  • A state list tagged by ownership constrains how components are written, and a team used to component-local state will find it restrictive until the first conflict it prevents.
Misreads
  • "So design the state store before any component." Design the state *list*; the store, the framework and the component structure are the frontend domain's decisions and depend on the framework in use.
  • "Interactions means user stories." Interactions are finer: not "the user checks out" but "the user clicks Pay twice". The stories are the goal step; the interactions are what the state has to survive.
  • "The UI is unimportant." It is last, not unimportant. A state list with no good UI is a screen the customer cannot use; the chain just refuses to let the layout make the state decisions.

Where this applies

Problem-solving advice is stated as universal far more often than it is. These labels say what each method is specific to — and where CONTESTED appears, the note gives the strongest form of the opposing view.

  • GENERALUser Goal → Interaction → State → UI applies to any interface — web, mobile, terminal — because every interface holds state on behalf of a user; what differs is which interruptions are possible.
  • STAGE-SPECIFICGreenfield, the chain designs the screen's state from scratch. In an existing frontend, the state architecture exists and the chain becomes: which existing rows does this screen read, which new rows does it add, and who owns the new ones.
  • ILLUSTRATIVEThe checkout page, the price-change banner and the "new messages below" indicator are invented to show the chain producing state the layout would not; no real design is described.

Where the depth lives

This domain asks the question and hands the answer off by name.

Further
  • The frontend domain's state-categories lesson picks up where the state list ends; arrive with the rows tagged server, client and transient, and the categories have something to sort.