On this pageOverview
Architecture
In most TypeScript UI frameworks, each component manages its own state and effects. In Foldkit, there’s a single state tree. Every change flows in one direction through the same loop.
This pattern is called The Elm Architecture. You don’t need to know Elm — Foldkit adapts it for TypeScript and Effect.
Every Foldkit app runs the same loop. A Message arrives — the user clicked a button, a timer fired, an HTTP response came back. The update function receives the current Model and the Message and returns a new Model along with any Commands to execute. The view function renders the new Model as HTML. When the user interacts with the view, it produces another Message, and the loop continues.
Commands are descriptions of one-shot side effects — HTTP requests, focus operations, localStorage writes, navigation calls. The Foldkit runtime executes them and sends their results back as new Messages, feeding them into the same loop. Each Command carries a name, which surfaces in tracing and tests.
Subscriptions are continuous streams of Messages from external sources — keypresses, recurring timers, WebSocket frames, window resize events. Where a Command runs once and reports back, a Subscription stays active for as long as the Model says it should, and the stream decides which source events become Messages.
ManagedResources have an acquire/release lifecycle tied to Model state — a camera stream during a video call, a WebSocket connection while the user is on a chat page. The runtime acquires them when the Model enters the relevant state, releases them when it leaves, and dispatches Messages for each transition.
That’s it. Every state transition in your app flows through a single loop. There’s no action-at-a-distance, no hidden state mutation, no effect that runs outside the cycle. If you want to know how the app got into its current state, you follow the Messages.
The complete cycle looks like this:
+------------------------------------------+
| |
↓ |
Message |
| |
↓ |
+---------------+ |
| update | |
+-------+-------+ |
↓ ↓ |
Model Command<Message>[] |
| | |
| +-> Runtime -----------------------+
| |
+-> view -> Browser -> user events ----------------+
| |
+-> Subscriptions -> Stream<Message> -> Runtime ---+
| |
+-> ManagedResources -> lifecycle -----------------+Every path on the right side produces a Message that feeds back into update. Four sources — Commands, Subscriptions, ManagedResources, and the Browser — one loop.
Sitting beneath the loop are Resources: app-lifetime singletons like AudioContext, RTCPeerConnection, or CanvasRenderingContext2D that Commands draw on. Resources don’t produce Messages themselves — they’re the ambient dependencies the Message-producing parts need to do their work.
Each concept in one place, in plain terms:
| Concept | Definition |
|---|---|
| Model | The single data structure that holds your entire application state. |
| Message | A fact about something that happened — a button was clicked, a key was pressed, a request succeeded with a payload. |
| update | A pure function that receives the current Model and a Message and returns a new Model along with any Commands to execute. |
| view | A pure function that renders the Model as HTML. User interactions produce Messages that flow back into update. |
| Command | A description of a one-shot side effect. The runtime executes it and sends the result back as a Message. |
| Subscription | A continuous stream of Messages from an external source, active for as long as the Model says it should be. |
| Resource | An app-lifetime singleton — an AudioContext, an RTCPeerConnection, a CanvasRenderingContext2D — that Commands can draw on. A dependency, not a Message source. |
| ManagedResource | Like a Resource, but scoped to a window of Model state instead of the app lifetime — a WebSocket connection while the user is on a chat page, a camera stream during a video call. Commands access it as a typed service while it’s live; the runtime acquires it on entry, releases it on exit, and dispatches Messages for each lifecycle transition. |
| Runtime | The Foldkit engine that executes Commands, runs Subscriptions, manages resource lifecycles, and routes Messages back into update. |
Think of a Foldkit app like a restaurant. The waiter keeps a notebook — a running picture of everything happening right now. Table 3 ordered the salmon. Table 5 is waiting for dessert. When something happens — a customer flags the waiter, the kitchen rings the bell — the waiter hears about it, updates their notebook, and maybe writes a slip for the kitchen. The waiter doesn’t cook the salmon — they hand the slip to the kitchen, and the kitchen reports back when it’s done.
Messages work the same way. “Table 3 asked for the check” is a fact given to the waiter, not an instruction. The waiter decides what to do — maybe bring the check immediately, maybe offer dessert first. The message stays the same either way.
The restaurant analogy
This analogy maps to every concept you’ll encounter in Core Concepts. The table below is a reference you can come back to as you read.
| Foldkit | Restaurant |
|---|---|
| Model | The waiter’s notebook — the current state of everything |
| Message | Something that happens: “table 3 asked for the check” |
| update | The waiter — hears what happened, updates the notebook, maybe writes a slip |
| view | What the customers actually see — plates on the table, the check arriving |
| Command | A slip for the kitchen: “prepare the salmon” |
| Subscription | A standing order: “keep the coffee coming for table 5” |
| Resource | Kitchen equipment — the oven, the stand mixer, the deep fryer. Turned on when the kitchen opens and available to every dish. |
| ManagedResource | A specialty station — set up when the menu features the seafood special, broken down when the special ends |
| Runtime | The kitchen — does the work, reports back when done |
That’s the architecture in the abstract. The next page shows a complete counter application — and you’ll see each of these pieces in the code.