Synchronizing UI and Server State

Server-side postback screens were limited, but they kept one execution path. Once Ajax became the main update mechanism, keeping display, input state, and server truth aligned became a structural problem.

This is the tenth post in Problems Cotomy Set Out to Solve. This continues from Screen State Consistency in Long-Lived UIs .

In the previous post, I focused on state ownership inside long-lived screens. That naturally leads to the next issue:

how the UI and the server fall out of agreement once one screen starts loading and saving through separate paths.

Once load and save paths diverge, UI and server state consistency becomes a structural problem.

A Practical Starting Point

The first Web system I built for my own independent work was an e-commerce site for second-hand goods. It was outside my company work, and I built it with very limited frontend experience. The stack was PHP, Smarty, and jQuery on top of an OSS package that already matched the business shape I needed.

That project influenced my early Web architecture more than I understood at the time. Because I had to customize the package heavily, I ended up learning not only how to make pages appear, but also how fragile a screen becomes once several different update paths accumulate around one business flow.

Why Server-Side Postback Was More Stable Than It Looked

That first e-commerce site was built almost entirely around server-side POST and server-rendered output. As far as I remember, it did not rely on Ajax for the core flow.

jQuery was there, because at the time it was everywhere, but the client-side processing stayed thin. The server controlled almost all display and registration behavior. There were small client-side helpers such as postal-code-based address lookup and screen switching by select value, but they did not own the business state of the screen.

That model had obvious UI limitations. Still, it had one major advantage: the path that displayed data and the path that saved data were almost the same path.

As a result, the screen could certainly have bugs, but it was less likely to show one thing while the server believed another. Unless I introduced a defect myself, the screen usually did not drift into strange visible behavior. That was a very practical form of reliability.

The Moment Ajax Changed the Problem

Later, I built a new system for operational reporting that had to run on smartphones. If I could have chosen freely, I would rather have built it as a separate desktop client in a three-tier client-server model. But smartphone-based reporting was a hard requirement, and distributing a dedicated application was not realistic in that environment.

So I built it as a Web system.

Around that time, I had already grown to dislike the ordinary pattern of filling a form and posting the whole page. One reason was simple: after submission, browser back behavior became awkward. That was enough reason for me to move registration to Ajax.

The problem is that Ajax submission is not difficult by itself. What becomes difficult is everything around it.

Once saving happens through Ajax, the frontend suddenly owns more of the coordination problem. And if loading still happens differently from saving, the screen ends up with two interpretations of the same data.

That is where synchronization problems begin.

If the screen loads through one rendering path and saves through another, it becomes possible for the same value to appear differently before and after save. Those inconsistencies are particularly dangerous because they are easy for tests to miss.

Why This Became Hard to Control

I built several systems with Smarty and jQuery. Smarty itself was not the real problem. It was just one server-side rendering approach.

The harder part was trying to keep one screen coherent while DOM operations, event handlers, and Ajax callbacks kept accumulating. That is manageable while the screen is small. But when the screen grows, procedures scatter and side effects begin to define the runtime more than the screen structure itself.

I tried many ways to organize that over time. Most of them eventually required patching and then produced another problem. The least fragile approach I found was to treat almost everything as events and continue attaching application-specific attributes as event targets.

Even so, it did not scale well enough for me. The systems were not especially large, yet I kept spending too much time investigating how the current behavior had been assembled from execution order and local side effects.

That experience clarified something important for me: the main difficulty was not simple lack of frontend knowledge. The deeper issue was that JavaScript and jQuery alone did not give me a structure I could trust for larger business screens without accumulating procedural drift.

Synchronization Is Not Just an Update Problem

The central failure pattern was not merely that Ajax existed. It was that load and save stopped sharing one operational contract.

The old server-side postback model had one important discipline: the server received the request, decided the state, and rendered the next screen. Once Ajax entered the screen, that discipline disappeared unless I rebuilt it explicitly.

If the real goal is to build a large client-side application with rich internal state, component-oriented frameworks such as React provide a very strong model. They give teams a disciplined way to structure rendering, state flow, and UI composition at that scale.

But that is not automatically the right fit for every business system. When the job is to build many small operational screens, each tied closely to server-side behavior, that model can become heavier than the screen itself requires. It also moves more of the screen construction boundary to the client, which can create another kind of split between server-side business flow and UI behavior.

So the issue is not whether component frameworks are good. The issue is whether the screen actually needs a client-side application model that large.

The structural problem can be stated more directly: UI state and server state exist independently. Load and save paths do not share a single contract. Synchronization is not enforced by the runtime. Consistency depends on implicit timing and local patches.

The problem can be summarized like this:

flowchart LR
    ServerLoad["Server load path"]
    UI["Visible UI and inputs"]
    AjaxSave["Ajax save path"]
    Patch["Local DOM patch"]

    ServerLoad --> UI
    UI --> AjaxSave
    AjaxSave --> Patch
    Patch --> UI

This looks small, but it creates a structural split. If the Ajax response is patched into the screen informally, the save path and the initial load path are no longer the same system.

That is exactly where the UI and the server start disagreeing.

What TypeScript Changed for Me

TypeScript did not solve architecture by itself, but it gave me a workable foundation for building one.

The important change was not only static typing. It was that I could finally structure the frontend as code I could continue to maintain. Classes, explicit contracts, and controlled composition made it far easier to stop scattering behavior across ad hoc handlers.

After moving to TypeScript, I eventually rebuilt most of the frontend of the systems I managed over roughly five years. That involved risk and some unreasonable effort, but the alternative was to continue carrying systems whose complexity kept increasing without limit.

At that point, rebuilding was the safer long-term decision.

How Cotomy Narrows the Synchronization Surface

Cotomy was one answer to that experience. Its role is not to eliminate all frontend state. Its role is to narrow the number of unofficial paths through which a screen can change.

Another important point is that Cotomy does not try to solve this by adding a separate client-side store model for each screen.

That matters not only because it reduces state fragmentation, but also because it keeps the screen itself readable as one recognizable unit. A screen is understood through a small number of explicit parts: page entry, page-level coordination, form lifecycle, and rendering.

This is important for more than reuse. It makes the overall runtime easier to understand intuitively. Instead of each screen becoming its own local architecture, screens can be read through the same structural classification. That lowers the cost of both maintenance and diagnosis.

This is also why the form hierarchy matters.

Cotomy does use inheritance, but not only as a code-sharing technique. It helps screens fall into recognizable operational categories.

A query form, an entity detail form, and a fill-and-render form are not just different implementations. They represent different kinds of screens with different runtime roles.

That matters because the screen stops being an arbitrary collection of event handlers. It becomes a member of a small number of understandable screen types. In practice, that makes the architecture easier to read, easier to extend, and easier to diagnose.

This matters even more in larger projects. When screens are kept within a small number of recognizable shapes, the system becomes dramatically easier to scale in practice. Teams can build, review, and maintain more screens without each one becoming a new local architecture.

There is a tradeoff here. A stricter screen model can reduce how much freedom is available for highly custom visual expression, and in some cases that can work against building the most cognitively refined UI for a specific screen.

But under real constraints such as limited budget, limited time, and limited human resources, this kind of structural control is often the more effective choice. It increases the size of the system a team can realistically sustain.

The synchronization contract becomes easier to see when the screen is read as a runtime sequence. Page entry initializes the controller. Ready marks the point where startup has completed. If the form already has an entity key, the screen can load current data through the API, fill matching inputs, and apply the same response to display-oriented DOM. If the form submits a newly created entity and receives 201 Created, the key is read from the Location header and later submissions move onto the update path. That is not a universal state system. It is a narrower contract that keeps create, load, fill, render, and update closer to the same model.

In the actual implementation, CotomyPageController is registered once through the page entry point. Its initializeAsync method runs from the page load path, and the ready event is triggered only after that initialization completes. That gives the screen a defined startup boundary instead of many unrelated startup fragments.

Form handling is also narrowed. CotomyForm standardizes submission as one lifecycle. CotomyApiForm turns the form into a FormData-based API submission path. CotomyEntityApiForm keeps the entity identifier on the form and switches from POST to PUT when the server has issued identity through the Location header.

Most importantly for this topic, CotomyEntityFillApiForm does not invent a separate client state store. When an entity key is present, it loads through the API when the page becomes ready, fills matching inputs, and applies response data to display elements through its renderer. That means the same response model can feed both input state and visible display state through one explicit runtime path.

This is also why the distinction between load path and save path matters so much. If one Ajax callback updates only a status label, another path fills inputs, and a third path redraws display fields, the UI may look coherent only by accident. Cotomy does not prevent every bug, but it tries to make those paths explicit enough that the screen can return to one recognizable contract after each operation.

This is the design point that mattered to me. The screen should not be kept in agreement by many unrelated jQuery patches. It should be brought back into agreement by a smaller number of named structures with defined ownership.

The Boundary Still Matters

This does not mean Cotomy solves synchronization by hiding the server behind a frontend-only abstraction. The server remains the authority for persisted business data.

Cotomy only narrows the UI-side execution paths. The page controller owns startup and page-level orchestration. The form owns submit and reload behavior. The renderer owns projection into display-oriented DOM.

That boundary matters because it keeps the responsibility readable. If the screen shows the wrong persisted value, the server contract or reload path should be examined first. If inputs fail to reflect the response correctly, the form fill path is the next place to inspect. If a display block differs from the input model, the renderer path is the likely source.

That is far easier to reason about than a screen assembled from scattered handlers and local patches.

Conclusion: Synchronization Needs a Shared Contract

My early Web systems taught me two different lessons.

Server-side postback was clumsy, but it stayed structurally honest because load and save were close to the same runtime path. Ajax improved interaction, but it also made it easy for the UI and the server to drift apart unless the screen had a clear synchronization contract.

That is why this problem belongs in the series. The real issue is not whether the screen uses Ajax. The issue is whether loading, saving, filling, and rendering are part of one understandable model.

Cotomy’s response is deliberately narrow. It does not try to turn the whole frontend into a universal client-state machine. It tries to define the page entry, form lifecycle, and rendering path clearly enough that a business screen can stay explainable after years of maintenance.

For me, that was the real requirement. I did not need more local patches. I needed a structure that made synchronization failures easier to prevent, easier to detect, and easier to repair.

The practical rule is simple. Load and save must share a contract. State mutation must have a defined entry point. UI must not update through informal paths.

Problem Series

This article is part of the Cotomy Problem Series, which examines recurring structural failures in business UI design.

Series articles: HTML and CSS as One Unit , Form Submission as Runtime , Screen Lifecycle and DOM Stability , Form State and Long-Lived Interaction , API Protocols for Business Operations , Runtime Boundaries and Operational Safety , UI Intent and Business Authority , Binding Entity Screens to UI and Database Safely , Screen State Consistency in Long-Lived UIs , and Synchronizing UI and Server State.

Next

Next: Where State Actually Changes

Learn Cotomy

Cotomy is a DOM-first UI runtime for long-lived business applications.