The Stario way
This is the overarching idea. Stario exists to teach the internals: how the work actually works. It is not a private interpretation you have to adopt. It is an attempt to go down until you hit a spec, a protocol, or a well-defined procedure — then compose those pieces so you can build without extra hassle.
Framework abstractions are interpretations. Interpretations change as you learn. The deeper you go, the more you can keep.
This page is a direction, not a law. It is influenced by The Tao of Datastar, because Datastar shaped how Stario thinks about the browser, state, and realtime.
If you're not learning, it's the wrong way
The goal is to make you a better developer. Not how to do X in Stario. How to do X well.
It is thin on purpose. It tries to arm you with durable ideas: HTTP request handling, response writing, routing, telemetry, compression, CQRS.
The goal is not to trap you inside Stario. Understand the work well enough to build with it today. Choose something else when that is better. It is fine to outgrow it. You should be able to see a better fit and leave.
If an abstraction stops you learning what is happening, it is probably the wrong abstraction.
Lean on the browser
Do not reinvent what the browser already does well. Requests, history, the DOM, signals — the browser is already optimized for those.
The fastest, simplest solution uses the least JavaScript. JavaScript should talk to the browser. It should not decide how the site looks. Do not juggle HTML in script. Send HTML. Update CSS. Morph the tree.
Datastar is that thin client. Use it to ask the browser to do browser work.
Put state in the right place
What the user can do lives on the backend. Run the command there. Update the frontend so it reflects what they see.
Think of it like a game loop: the server holds truth; the view is a function of that truth. Signals are useful as a UI mirror and as extra context on a command. They are not the system of record.
Think multi-page application
Build the app as resources, in CQRS terms: a first document, a subscribe stream that owns the view, short commands that validate, reply, then work.
That shape gives you realtime when you want it, and collaboration when you want it. You do not start with a client-rendered SPA. You add a stream when live updates earn their complexity.
Each page is a resource. Keep a stream open on the current state of that resource when the tab should stay live. Ship, measure, repeat.
The inefficiency of this approach is mostly in your head. Compression and CSS view transitions can make navigation feel responsive. Where an MPA is a poor fit — heavy client-only interaction, offline-first — measure and choose a different shape.
Features register themselves
A feature knows what it needs: its routes, its handlers, its views. Integration is a register function — or a small router object — that declares those parts and attaches them to the app.
That is the opposite of mounting. You do not take a pre-shaped subtree and drop it onto a prefix. You have a well-defined set of parts. A register method uses them.
It asks more of you. You see every attachment. You get finer checks and finer control over the things that matter.
def register_room(app: App, db, relay) -> None: app.add(ROOM, room_page(db)) app.add(SEND, send(db, relay)) app.add(SUBSCRIBE, subscribe(db, relay))Chat room is that tree. Structuring apps is the file layout. Routing is the contract.
Where next
Handlers and the writer — the handler starts when headers are in, and can write for as long as you want.
Go-to architecture — subscribe, commands,
ui = f(state).Telemetry design — close the development loop.
Realtime tiles — one resource in one file.
Chat room — the same idea as a larger project.