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.

python
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