Bundle / foldkit-bundle

Reacting to a child's Messages

An OutMessage is what a child chooses to tell its parent. Sometimes the parent must react to what the child received instead: a socket's Opened or Failed, a media query's change. onMessage in the placement config sees each of the child's Messages as a Step of the parent, after the child has handled it and after its onOut:

ts
const Observed = Page.assemble(
  Page.at(Dark, { args: { query: '(prefers-color-scheme: dark)' } }),
  Page.at(Narrow, {
    args: { query: '(max-width: 40rem)' },
    // Help closes when the window becomes narrow.
    onMessage:
      ({ matches }) =>
      model => ({ model: matches ? { ...model, helpOpen: false } : model }),
  }),
)

It observes; the child still owns its own transition, and a helper, which is not a Message, does not reach it. A collection's onMessage also receives the item's key.

The parent's own update, given to placements.update(own), never receives a placement's wrapper: the placement takes it. Its Message is typed that way, as Bundle.OwnMessage<Message, typeof placements.placements>, so an exhaustive match over it needs no arm for GotDarkMessage. Match it with Effect's Match.valueTags, which is exhaustive over the union it is given; a union's own Message.match asks for every variant of the whole union.

ts
type OwnMessage = Bundle.OwnMessage<Message, typeof Observed.placements>

const updateOwn = (model: Model, message: OwnMessage) =>
  Match.valueTags(message, {
    ClickedHelp: () => ({ model: { ...model, helpOpen: true } }),
  })

Observed.update(updateOwn)

A wiring's tags are strings at runtime, so a Message a wiring claims still appears in OwnMessage.

Presets

bundle.with(args) binds the args, so a placement gives none:

ts
const PrefersDark = MediaQuery.with({ query: '(prefers-color-scheme: dark)' })
Page.place(PrefersDark, 'dark')

Page.place(bundle, field, config) is Page.at without a separate declaration, for a Message union built another way.