Hybrid regions
A hybrid UI keeps chrome on the client (inputs, layout, local Squawks) and owns a region on
the server. The client mounts serverRegion("messages") (an empty #messages container). The
server fills it with AscentDatastar.patchRegion("messages", ui).
Docs pages have no live datastar server, so the client shape is shown as source only. For a
working chat, see example/hybrid-chat.
Client declaration
E.div(
E.h2("Chat"),
serverRegion("messages"),
E.input(A.placeholder("type…")),
)
Server patchRegion
patchRegion targets #id with inner mode by default; the same id the client mounted.
{
val list = E.ul(E.li("hello"), E.li("world"))
val sse = events {
AscentDatastar.patchRegion("messages", list)
}
(for
response <- sse.run(Request.get("/sse"))
body <- response.body.utf8
yield (
body.contains("event: datastar-patch-elements"),
body.contains("data: selector #messages"),
body.contains("hello"),
)).orDie
}(true,true,true)Scoped live server
Same Scope-bound pattern as the counter: start routes, POST a message, confirm OK. Full chat
wiring lives in example/hybrid-chat.
{
def pushMessages(msgs: Ref[Vector[String]]): ZIO[Datastar, Nothing, Unit] =
msgs.get.flatMap { items =>
AscentDatastar.patchRegion(
"messages",
E.ul(items.map(s => E.li(s))*),
ascent.datastar.ElementPatchMode.Inner,
)
}
def routes(msgs: Ref[Vector[String]], pulse: Hub[Unit]): Routes[Any, Nothing] =
Routes(
Method.GET / "sse" -> events {
for
_ <- pushMessages(msgs)
stream <- pulse.subscribe.map(zio.stream.ZStream.fromQueue(_))
_ <- stream.mapZIO(_ => pushMessages(msgs)).runDrain
yield ()
},
Method.POST / "send" -> handler { (_: Request) =>
(msgs.update(_ :+ "ping") *> pulse.publish(()).unit).as(Response.ok)
},
)
for
pulse <- Hub.unbounded[Unit]
msgs <- Ref.make(Vector("welcome"))
result <- ZIO
.scoped {
Server.install(routes(msgs, pulse)).flatMap { server =>
server.port.flatMap { port =>
Client.request(Method.POST, s"http://127.0.0.1:$port/send").map(_.status)
}
}
}
.provide(Server.defaultWith(_.port(Port(0))))
yield result
end for
}Status(200,OK)