This project is a message-driven, unidirectional architecture inspired by The Elm Architecture (TEA) for Svelte 5. The core principles are making state transitions explicit, predictable, and testable, while keeping side effects centralized and easy to reason about.
Users can select between cats and dogs, and the app fetches data accordingly. The design emphasizes fearless refactoring through explicit messaging and unidirectional data flow.
An app is fundamentally a sequence of state and commands over time. Each message describes what happened, driving pure state transitions and triggering explicit commands that represent real side effects like data fetching.
This philosophy enforces:
$inspect(frames)The application is built around a few simple but powerful ideas:
Messages as the Driver of Change
All interactions—user input, async results, and external events—are represented as typed messages (Msg). Messages describe what happened, not what to do.
Pure State Transitions
A single pure function (computeNextModelAndCommands) takes a message and returns:
ModelCmd values describing side effectsExplicit Commands for Side Effects
Side effects such as HTTP requests are modeled as data (Cmd) and interpreted in exactly one place. Logging commands are not recommended except for possible temporary debugging.
Unidirectional Data Flow
View → Msg → (Next Model + Next Commands) → (Replace Model + Run Commands) → Derived State → View
Data flows in one direction. There are no hidden mutations or implicit effects.
Development-Time Frame Inspection
Using Svelte 5’s $inspect(frames), the app records and exposes a history of frames (state snapshots, messages, and commands) during development, enabling easier debugging and understanding of state evolution over time.
The core protocol of the app remains:
Model — the complete state of the application, including HTTP request status and fetched dataMsg — all valid messages the app can respond toCmd — explicit descriptions of side effectsNextModelAndCommands — the pair of model and command results from handling a messageFrame — the recorded sequence of message and state snapshots for development-time inspectionAt the heart of the app is a pure transition function:
const computeNextModelAndCommands = (msg: Msg): NextModelAndCommands => { ... }
This function:
Pattern matching is done with matchStrict, so every valid message must be handled explicitly. This enforces comprehensive handling and guides refactoring.
Side effects are modeled as a focused Cmd union:
type Cmd =
| { kind: 'SelectCats' }
| { kind: 'SelectDogs' }
| { kind: 'FetchAnimal' }
Commands are not part of the model. They are instructions for the runtime to perform real side effects such as HTTP fetching.
A single interpreter function executes commands:
const executeCommand = (cmd: Cmd): void => {
// perform the fetch effect
}
This keeps all impure behavior localized and auditable.
User interactions, keyboard shortcuts, and async fetch results are all turned into messages:
This keeps the entire control flow explicit and centralized.
The main component is organized into these sections:
Keeping these concepts close together makes the architecture easy to follow and modify.
This project prioritizes:
$inspect(frames) to trace frame history during development.This project is:
It is not:
The goal is clarity, not abstraction for its own sake.
This codebase favors:
If you’re interested in functional state modeling, exhaustive pattern matching, or applying Elm-inspired ideas in a Svelte context, this project is meant to be a readable and adaptable starting point.