Sub

:= [
    Every({ ms : U32, on_tick : Box(I64 -> Box(msg)) }),
    Keyboard({ event : Str, keys : List(Str), prevent_default : Bool, on_key : Box(KeyEvent -> Box(msg)) }),
    PortListen({ name : Str, on_value : Box(Str -> Box(msg)) }),
    UrlChanged({ on_change : Box(Str -> Box(msg)) }),
]

A subscription: a recurring event source declared as *data*, returned from subscriptions and managed by the host. While a subscription stays in the returned list it keeps firing; leaving it out of the list stops it, so cancellation is by omission and nothing leaks. The callback is a real typed function, boxed so the host can store and call it without knowing the app's Msg layout.

Identity is the variant plus its parameters (for Every the interval, for PortListen the port name). A model change that alters the parameters stops the old source and starts a fresh one; re-declaring the same identity keeps the running source and only swaps in the new callbacks (which may capture new model state). Two subscriptions with identical parameters share one underlying source (timer, document listener, port registration), and each firing delivers every declared callback's message, in declaration order.

Apps normally use the constructors in Time, Keyboard, Port and DOM rather than these variants directly. Payloads are records so the host reads named fields.

map : Sub(a), (a -> b) -> Sub(b)

Re-target a subscription to a parent message type. Note the mapped callback is a new box each render, which is fine: sub identity comes from the variant's parameters, never the callback.

KeyEvent : {
    key : Str,
    code : Str,
    ctrl : Bool,
    shift : Bool,
    alt : Bool,
    meta : Bool,
    repeat : Bool,
    is_composing : Bool,
}

The keyboard event record delivered to key handlers, document-level (the Keyboard module) and element-level (Attribute.on_key and friends) alike; annotate handlers as Sub.KeyEvent. key is the logical key ("a", "Enter", "Escape", ...): it follows the active layout and modifiers, so Shift makes "a" arrive as "A". code is the physical key ("KeyA", "Space", "ControlLeft", ...): it ignores both, which is what layout-independent controls (say, WASD movement) want. repeat is true on the auto-repeated firings of a held key; skip those to react once per press. The four modifier flags say whether that modifier was held when the event fired, so a Ctrl+S shortcut is if e.ctrl and e.key == "s". is_composing is true while an IME composition session is in progress (the browser reports such keydowns as key "Process"); skip those to ignore the intermediate keystrokes international text input is assembled from.