Handlers

An ActionHandler[M, V, E] is a partial function from action to IO[E, ActionResult[M, E]], scoped to slice V. Inside handle(lens) { ... } the lens is ambient: update, updated, and noChange read and write that slice.

update, updated, noChange

noChange produces a clean ActionResult. Dispatch-level FastEq then skips every listener.

for
  c <- Conduit(State(0, "x"))(countHandler)
  _ <- c(Op.Inc, Op.Inc, Op.Dec, Op.TouchOnly)
  _ <- c.run()
  s <- c.currentModel
yield s.count
1

Live: focus and follow-ups

Inc / Dec / Reset move count. Set label writes label through focus. Inc twice increments once in the handler and returns Inc as ActionResult.next, so one click becomes two dispatches.

Mermoid.diagram(composeDiag)
for
  (_, ctx) <- DocsRuntime.live(State(0, "x"))(focused)
  count    <- ctx.squawk(_.count)
  label    <- ctx.squawk(_.label)
yield E.div(
  E.p("count: ", E.strong(count.map(_.toString)), "  label: ", E.strong(label)),
  E.button(Events.onClick(_ => ctx(Op.Dec)), "−"),
  E.button(Events.onClick(_ => ctx(Op.Inc)), "+"),
  E.button(Events.onClick(_ => ctx(Op.IncTwice)), "Inc twice"),
  E.button(Events.onClick(_ => ctx(Op.ResetCount)), "Reset"),
  E.button(Events.onClick(_ => ctx(Op.SetLabel("on"))), "Label on"),
  E.button(Events.onClick(_ => ctx(Op.SetLabel("off"))), "Label off"),
)

count: 0 label: x

for
  c <- Conduit(State(0, "x"))(focused)
  _ <- c(Op.IncTwice)
  _ <- c.run()
  s <- c.currentModel
yield s.count
2

>> vs ++

>> (orElse) is first-match composition: split handlers by slice, try the left one, then the right. ++ (fold) runs both when they match the same action. The second sees the first's model; next concatenates; dirty is OR so update ++ noChange still notifies.

Mermoid.diagramInteractive(composeDiag, initialWidth = 560)
viewport 560px

Unhandled and errors

The default unhandled path is a defect. onUnhandled maps the action to a typed E. widen[E2] is for composing a Nothing handler with one that can fail. effectOnly runs a side effect and returns a clean result; it always sees the whole model, even inside focus.