packages feed

miso-1.15.0.0: CHANGELOG.md

# Changelog

All notable changes to `miso` are documented here.

## 1.15.0.0

### Added

- **`rAFLoop`** (`Miso.Subscription.Util`). A self re-scheduling
  `requestAnimationFrame` loop as a `Sub`, with one callback for the whole
  loop: `rAFLoop :: IO (Double -> IO ()) -> Sub model action`. The `IO`
  action runs once at start and builds the per-frame handler, so it is the
  place to acquire what the handler needs. `rAFSub`, `rAFSubElapsed` and
  `canvasSub` are now built on it.

- **`Functor` instances for `View` and `Attribute` (non-`native` builds).**
  `fmap` / `<$>` / `<$` map the `action` a view or attribute produces, so a
  sub-view written against one `action` type can be embedded in another
  (`ChildAction <$> Child.view m`). These existed before 1.13.0.0 and were
  dropped when the Lynx dual-thread runtime landed. Mapping stops at component
  boundaries (`VComp` / `VCompStatic` keep their own `action`), and passes
  through the ambient accessors `VContext` / `VProps` / `VModel`. **Not
  available under the `native` flag**: a main-thread (`OnStatic`) handler
  crosses threads as a bare `StaticKey` that the main thread re-dispatches at
  the owning component's `action` type, and an arbitrary mapping function
  can't travel with it. See `Note [Functor View]` in `Miso.Types`.

- **`Miso.UUID`.** A `UUID` type backed by `MisoString`, with an API that
  mirrors `Data.UUID` from the `uuid` package: `toString` / `fromString`,
  `toText` / `fromText`, strict and lazy ASCII `ByteString` conversions,
  `toByteString` / `fromByteString` (16 bytes, network order), `toWords` /
  `fromWords`, `toWords64` / `fromWords64`, `nil` and `null`. `nextRandom`
  generates a v4 UUID with the browser's `crypto.randomUUID()`, which is only
  available in secure contexts (HTTPS or `localhost`). Parsing accepts the
  `8-4-4-4-12` form in either case and normalizes it to lowercase; `Show` and
  `Read` use the unquoted form, like `Data.UUID`. Instances are provided for
  `ToMisoString` / `FromMisoString`, `ToJSON` / `FromJSON`, `ToJSVal` /
  `FromJSVal`, and `Router` (as a route capture).

- **MicroHs backend (alpha).** miso compiles with
  [MicroHs](https://github.com/augustss/MicroHs), to WebAssembly or
  JavaScript through emscripten, and passes the integration test suite on
  both. The flake provides MicroHs with miso installed (`nix develop .#mhs`),
  and the [`miso` CLI](https://github.com/haskell-miso/cli) builds miso
  applications with it without GHC or Nix.

- **`CSS.content`** (`Miso.CSS`). The
  [`content`](https://developer.mozilla.org/en-US/docs/Web/CSS/content)
  property: `content :: MisoString -> Style`.

### Removed

- **Breaking: `mountStaticWithProps`.** It was a deprecated synonym for
  `mountStatic` since 1.14.0.0, when `mountStatic` began accepting components
  with `props`. Replace it with `mountStatic`; call sites are otherwise
  unchanged.

### Changed

- **Printed query strings list entries in key order, joined with `&`.**
  Flags used to come after the params as separate `?`-groups. Code that
  matches on the exact output of `prettyURI`, `prettyRoute` or `href_` may
  need updating; see the matching entry under Fixed.

- **Breaking: `uriFragment` no longer includes the leading `#` (#1696).**
  `parseURI` (and so `getURI`, `uriSub` and `FromJSON URI`) stored the
  fragment as `"#cool"`, contrary to the documented contract, and `toURI` on a
  route with a `Fragment` field stored `"##intro"`. `prettyURI` appended the
  field as-is, so a hand-built `URI "foo" "section-1" mempty` printed as
  `/foosection-1` and `prettyRoute` printed `##intro`. The fragment is now
  stored without the `#`, and `prettyURI` adds one when it is non-empty.
  URIs printed from parsed input (`getURI`, `uriSub`, `routerSub`, `route`)
  are unchanged.

  Migration: stop stripping `#` from a parsed `uriFragment`, and compare
  against the bare value (`uriFragment uri == "top"`). Fragments written with
  a leading `#` (`uri { uriFragment = "#top" }`) still print correctly, since
  `prettyURI` doesn't repeat an existing `#`.

- **Event handlers run synchronously, during dispatch, on every backend.**
  `on` / `onWithOptions`, event delegation and `addEventListener` (and the
  subscriptions built on it: `windowSub`, `windowSubWithOptions`,
  `keyboardSub`, `arrowsSub`, `onLineSub`, `uriSub`, ...) now call their
  callback synchronously. On WASM they were asynchronous, so a handler could
  run after the browser had finished dispatching the event: `preventDefault`
  / `stopPropagation` then did nothing (#1334), and event data that only
  exists during dispatch (a `DataTransfer`) was gone. On WASM, a synchronous
  callback is now followed by a round of the RTS scheduler loop, so the
  actions it queued are processed right away (#1683). A callback passed to
  `addEventListener` must not block (`threadDelay`, `await`, `takeMVar`,
  ...): on WASM, `threadDelay` or `await` in a synchronous callback raises
  `WouldBlockException`, which stops the Haskell runtime if uncaught; on the
  JS backend the callback is silently abandoned. Hand blocking work to the
  `sink` (or `forkIO` it) instead.

- **Breaking: `OnStatic` exists only under the `native` flag.** Elsewhere
  `event` now dereferences the `StaticPtr` itself and builds a plain `On`.
  Runtime behavior is unchanged — no web backend ever dispatched by
  `StaticKey` — but code that pattern-matches on `OnStatic` must be guarded
  with `#ifdef NATIVE` (or match `On` instead).

- **Breaking: GHCJS 8.6 is no longer supported.** The GHC JavaScript backend
  (`arch(javascript)`) is now the only JS target. The `GHCJS_OLD` code paths,
  `impl(ghcjs)` conditionals and the `ghcjs-prim` dependency are removed, along
  with the legacy nixpkgs pin (`legacyPkgs`) and its GHCJS 8.6 / GHC 8.6.5
  Nix targets.

- **`MisoString` on the WASM and MicroHs backends now behaves like
  `Data.Text`**, which is what it is on the server, so client and server
  agree. Positions and lengths count code points everywhere (`head`, `last`,
  `tail`, `init`, `uncons`, `index`, `takeEnd`, `dropEnd`, `cons`, `snoc`,
  `singleton`, `intersperse`, `justify*`, `maximum`, `minimum` used to see
  the two halves of an emoji separately), and:
  - `lines "a\n" == ["a"]` and `lines "" == []` (no trailing empty line);
  - `words`, `strip`, `stripStart` and `stripEnd` use `isSpace`: `words`
    splits on tabs and newlines too, and `strip` no longer removes U+FEFF,
    U+2028 or U+2029;
  - `replace` replaces every occurrence, literally (it replaced only the
    first, and expanded `$&` in the replacement);
  - `split p s` no longer appends an empty piece when `s` has no separator;
  - `stripSuffix ""` and `dropEnd 0` / `takeEnd 0` leave the string alone;
  - `commonPrefixes "foo" "foobar"` is `Just ("foo", "", "bar")`, not
    `Nothing`;
  - `chunksOf k ""` is `[]`, and `chunksOf` with `k < 0` no longer loops;
  - `unfoldrN` keeps its characters in order and stops at `Nothing`;
  - `pack`, string literals and `textToJSString` keep a leading U+FEFF (the
    `TextDecoder` dropped it);
  - `toLower` maps character by character (JavaScript's `toLowerCase`
    turned a word-final `Σ` into `ς`);
  - `head`, `last`, `init`, `index`, `maximum`, `minimum` and `count` fail
    with a Haskell `error` instead of a JavaScript exception (which is fatal
    on MicroHs);
  - `compare` (`Ord`) uses code point order, so `Map MisoString` keys order
    the same on client and server (JavaScript's `<` put emoji before
    U+E000 to U+FFFF). The GHC JavaScript backend, whose `JSString` comes
    from ghcjs-base, still compares code units.

- **`Miso.Canvas` operations call direct foreign imports.** Every
  straight-line `CanvasRenderingContext2D` operation (`fillRect`, `arc`,
  `moveTo`, `globalAlpha`, `fillText`, ...) has its own import in
  `Miso.Canvas.FFI`; the generic path marshalled the method name and
  allocated a `JSVal` per argument on every call. `fillStyle`, `strokeStyle`
  and `shadowColor` with an `RGB` or `RGBA` colour pass the channels as
  numbers and build the `rgb(...)` string in JavaScript. The public API is
  unchanged, and native GHC keeps the generic calls.

- **Lynx toolchain and SDK bumped.** `@lynx-js/rspeedy` 0.18.4,
  `@lynx-js/react-rsbuild-plugin` 0.22.1, `@lynx-js/react` 0.129.1,
  `@lynx-js/types` 4.4.2 and `@lynx-js/type-element-api` 0.0.12. The sample
  native hosts now share one SDK version, 4.0.3 (Android was on 3.8.0, iOS on
  4.0.0), the newest stable release published on both Maven and CocoaPods.

### Fixed

- **JS backend: `freeFunction` releases the callback.** It was a no-op, and
  the JS RTS keeps every callback as a permanent GC root until it is
  released, so each event handler freed on re-render, each mount / unmount
  callback and each rAF / canvas callback leaked, along with everything it
  closed over, for the lifetime of the page.

- **`createSub` keeps its thread's delay within a 32-bit `Int`.** The `Sub`
  thread parked itself with a `threadDelay` of 10^10 microseconds, which does
  not fit an `Int` on wasm32. GHC wrapped it silently; MicroHs checks `Int`
  arithmetic and threw `Overflow`, so the `Sub` was released right after it
  was acquired (a `windowSub` never received an event).

- **`stopSub` from an `rAFSub` / `rAFSubElapsed` / `canvasSub` action no
  longer crashes.** Each frame callback re-scheduled itself after
  dispatching, and `release` freed it. The wasm RTS has no timer, so it
  context-switches at every heap-block boundary when another thread is
  runnable; sinking the action woke the scheduler, which could run the
  `stopSub` and free the callback before the callback resumed and called
  `requestAnimationFrame` on its own freed handle (`getJSVal` throws, and
  the runtime is gone). The callback now owns its own lifetime: stopping the
  `Sub` sets a flag, and on its next frame the callback frees itself instead
  of re-scheduling, so it is freed one frame later than before and nothing
  else ever touches it. `rAFSubElapsed` also no longer allocates (and leaks)
  a new callback every frame. See `Note [rAFLoop teardown]` in
  `Miso.Subscription.Util`.
- **Empty query values parse.** After `=` the lexer needed at least one
  character, so `?a=&b` parsed as the flag `a` and dropped `b`, and a
  `QueryParam (Just "")` printed as `?q=` decoded back as `Nothing`. `?a=`
  now gives `a` the value `""`, so a `QueryParam "q" MisoString` route
  decodes `?q=` as `Just ""` instead of `Nothing`.

- **`ToMisoString (QueryParam p a)` prints `?key=value`.** It printed the
  value and key swapped, as `?value=key`.

- **`prettyQueryString` / `prettyURI` print one `?` and separate entries with
  `&`.** Flags were printed as extra `?`-groups after the params (`?a=1?flag`,
  `?a?b`), which a browser read as `a` = `1?flag` and as one key `a?b`. Every
  entry now follows a single `?`, in key order (`?a=1&flag`, `?a&b`), and
  entries with an empty key are skipped. This changes the output of
  `prettyRoute`, `href_`, `pushURI` / `replaceURI` / `pushRoute`, and the
  `ToMisoString` / `ToJSON` instances of `URI`. `parseURI` still accepts the
  old form.

- **A generic `Index` route keeps its query fields when encoded.**
  `fromRoute` emitted only `IndexToken` for an index constructor, dropping
  its `QueryParam` and `QueryFlag` fields, so `prettyRoute` and `href_`
  printed `/` where they should print `/?page=2`. Decoding already read
  those fields. (A `Fragment` field is now encoded too, but `Fragment`
  fields still can't be decoded.)

- **`QueryParam Nothing` is left out of the URL.** It was printed as a bare
  flag, so a route with an unset param linked to `/widget?bar` rather than
  `/widget`. This applies to every generic route, not just index routes. It
  now prints nothing; decoding a URL without the key still gives
  `QueryParam Nothing`, and so does a URL with the bare key (`?bar`), so
  existing links still parse. `fromRoute` and `toURI` change too: the key
  no longer appears in the token list or in `uriQueryString`.

- **The fragment lexer takes everything after the `#`.** It only accepted
  path-segment characters, so it stopped at the first `/`, `?`, `#`, `=`,
  `&`, space or `[`, and the rest was dropped or misparsed as path or query.
  `/#/users/1?tab=2` parsed as `URI "" "" mempty`, and `/foo#a/b` parsed as
  path `foo/b` with fragment `a`. The fragment lexer now takes the rest of
  the input verbatim, as browsers report it in `location.hash`, so
  `parseURI "/#/users/1?tab=2"` has fragment `/users/1?tab=2`, and `/foo#a/b`
  has path `foo`. `route` and `routerSub` ignore the fragment; to route on a
  `#/…` hash, run `toRoute` on `uriFragment`. (The query lexer still stops at
  a `/` or `?` inside a query value, so `/login?next=/home#tab` still loses
  everything from that `/` on.)

- **`eitherDecode` and `decode` return `Left` / `Nothing` on invalid JSON
  instead of throwing (WASM, JS backend, MicroHs).** They called
  `JSON.parse` through an `unsafe` import, and its `SyntaxError` escaped: on
  WASM the update that decoded the text was lost, and on MicroHs, where an
  exception in an `unsafe` import is fatal, the runtime stopped. The error
  is now caught in JavaScript, and `eitherDecode` returns
  `Left "eitherDecode: <message>"` with `JSON.parse`'s message, where it used
  to put the input string. Invalid input does not decode as `null`, so it is
  a `Left` for a `Maybe a` too. `VANILLA` builds already returned `Left`.

- **`toHtml` escapes attribute values.** Property values, class names and
  style declarations were written between quotes as they were, so a `"` in
  one ended the attribute, and user-controlled text could inject markup into
  prerendered HTML. They are now escaped with `htmlEncode`, as text nodes
  are. The browser decodes them back, so the DOM attributes and hydration are
  unchanged. If you escaped attribute values yourself to work around this,
  stop: they will now be escaped twice (`&amp;` renders as `&amp;amp;`).

- **`toHtml` renders a `textarea`'s value as its content.** Browsers ignore
  a `value` attribute on a `<textarea>`, so prerendering from any backend but
  `VANILLA` (where `textarea_` already moved the value) produced an empty
  textarea. The value is now rendered, escaped, after the textarea's
  children. An empty value leaves the textarea empty (on `VANILLA` it
  rendered as a single space, which became the textarea's value), and a
  leading newline survives parsing (the HTML parser drops the first one).

- **WASM: `startApp` no longer evaluates `miso.js` a second time after
  `withJS`.** `initComponent` (behind `startApp`, `miso`, `renderApp`,
  `prerender`, `reload` and `live`) evaluated it unconditionally, replacing
  `globalThis.miso` and dropping anything set on it in between. It now loads
  `miso.js` only if `withJS` has not. `renderApp` also loads `miso.js` before
  installing its drawing context: it wrote to `globalThis.miso` before
  anything had created it, so it always threw on WASM. The `withJS` Haddock
  now says that `main` must use it before calling anything that reads
  `globalThis.miso` (`Miso.FFI`, `Miso.Random`, `Miso.JSON.decode`, ...)
  ahead of `startApp`.
  
- **A tab, newline, form feed or carriage return in a class no longer
  breaks rendering.** `class_` / `classes_` / `classList_` strings were split
  on spaces only, so `"foo\tbar"` reached `classList.add` as one token, which
  throws `InvalidCharacterError` mid-patch, while server rendering of the same
  view worked. Classes are now split on ASCII whitespace, as the HTML parser
  splits a `class` attribute. Broken since 1.9.0.

- **`MisoString` on the WASM and MicroHs backends no longer does work one
  character at a time over the FFI.** `length` allocated an array of the
  whole string and `uncons` called it, so everything that walks a string was
  O(n²): `lines` over 110 KB took minutes. Functions that take a Haskell
  function (`map`, `filter`, the folds, `any`, `all`, `takeWhile`, `break`,
  `groupBy`, `split`, `zip`, the scans, ...) made three FFI calls per
  character; they now convert to `Text` once and slice the original string
  for the result. Searching and splitting (`breakOn`, `splitOn`, `lines`,
  `words`, `replace`, `strip`, `commonPrefixes`, ...) is one native call per
  piece. See `scripts/bench-jsstring` for the benchmark.

## 1.14.0.0

### Added

- **`VModel` / `vmodel` / `withModel`.** The `model` counterpart of
  `VProps`: an ambient accessor (not a node) wrapping a
  `model -> View context props model action` function, so a helper deep in a
  view tree can read the enclosing component's `model` without needing it
  threaded through as an explicit argument. Resolved against the mounting
  component's current `model` whenever the enclosing `View` is built, and
  against its initial (or hydrated) `model` when rendered with `toHtml`.
  `withModel` is a synonym for `vmodel`.
- **`svgWith_`** (`Miso.Native.X.Element.Svg`). Native inline SVG content
  that reads the enclosing component's `context` / `props` / `model`:
  `svgWith_ attrs content` is an `<svg>` that obtains all three ambiently via
  `withContext` / `withProps` / `withModel` and hands them to `content_`, so
  the content may use `vcontext` / `vprops` / `vmodel` directly.
- **`VProps` / `vprops` / `withProps`.** The `props` counterpart of
  `VContext`: an ambient accessor (not a node) wrapping a
  `props -> View context props model action` function, so a helper deep in a
  view tree can read the enclosing component's `props` without needing it
  threaded through as an explicit argument. `ImplicitParams` or a `Reader`
  could serve the same purpose, at the cost of a GHC-specific extension or a
  monadic style for view code. Resolved against the mounting
  component's current `props` whenever the enclosing `View` is built or
  rendered (`toHtml` on a bare `View` uses `()`). `withProps` is a synonym
  for `vprops`.
- **`toHtmlWith`.** `toHtmlWith :: context -> props -> model -> View context
  props model action -> ByteString` renders a `View` whose `context`, `props`
  or `model` type is not `()`, supplying the values `VContext` / `VProps` /
  `VModel` accessors resolve against.
- **`VContext` / `vcontext` / `withContext`.** An ambient accessor (not a
  node) wrapping a `context -> View context props model action` function, so a helper deep in a view tree can read the app-global
  `context` without needing it threaded through as an explicit argument.
  Resolved against the current `context` whenever the enclosing `View` is
  built or rendered; it does not itself trigger a redraw — that is still
  governed solely by `useContext`. `withContext` is a synonym for `vcontext`.
- **`onDestroyedWith`** (`Miso.Event`). Like `onDestroyed` but also receives
  the element's (already-detached) `DOMRef`, completing the lifecycle hook
  family alongside `onCreated` / `onCreatedWith` and `onBeforeDestroyed` /
  `onBeforeDestroyedWith`.

### Removed

- **Breaking: `setContext`.** It existed to seed the `globalContext` cell for
  the `ToHtml` renderer, and that cell is gone: the renderer now takes the
  `context` as an argument, `ToHtml` requires `context ~ ()`, and there is no
  cell at all until an app starts, so the call it was added for is now a type
  error rather than something you fix by calling it first. Seed the `context`
  with `startAppWithContext` / `misoWithContext` / `prerenderWithContext`,
  change it from `update` with `modifyContext` / `putContext`, and pass it to
  `toHtmlWith` for server-side rendering.

### Changed

- **Breaking: `view` takes only the `model`.** The `view` field of a
  `Component` was `context -> props -> model -> View context props model
  action`; it is now `model -> View context props model action`. The
  `context` and `props` are read where they are actually needed with the
  ambient accessors `withContext` / `vcontext` and `withProps` / `vprops`,
  which resolve at the point the enclosing `View` is built or rendered. A
  `view` that ignored them no longer has to name them (`\_ _ _ -> …` becomes
  `\_ -> …`), and one that used them reads them at the point of use instead
  of threading them down through every helper. `toHtmlWith` still takes the
  `context`, `props` and `model` — they are what the accessors in the
  rendered tree resolve against — but the `view` it is applied to now takes
  only the `model`: `toHtmlWith ctx props model (view comp model)`.

  The `model` stays an argument purely for convenience — it is the one of the
  three a `view` almost always needs. It is available ambiently too, so
  `view _ = withModel $ \m -> …` is equivalent to taking it as an argument;
  `withModel` / `vmodel` remain the better choice for a helper deep in the
  tree that the `model` is not otherwise passed to.

- **Breaking: the app-global `context` cell is owned by the app, not by a
  top-level CAF.** The `globalContext` `IORef` is gone. `initComponent` now
  creates the cell and every mounted component holds a *reference* to it as
  `_componentContext` (lens `componentContext`), so there is exactly one
  value per running app: no component holds a copy, none can disagree, and
  `modifyContext` (via the new `modifyContextAll`) is a single
  `atomicModifyIORef'` on that cell rather than a rewrite of every component.
  On Lynx each thread runs its own `initComponent` and so owns its own cell.
  `buildVTree` receives the context as an explicit argument, so `VContext` is
  resolved exactly like `VProps`, and the render path takes a plain value —
  no effectful read inside rendering, and no `undefined`-seeded global to
  force. The scheduler's propagation pass is unchanged. Consequences:
  `toHtmlWith` takes the `context`
  first (`toHtmlWith ctx props model view`) and `ToHtml (View …)` requires
  `context ~ ()` alongside `props ~ ()` and `model ~ ()`; the Lynx SVG
  `content_` takes the `context`, `props` and `model` ahead of the content
  `View`; `Miso.Reload`'s stable pointer no longer carries a context cell and
  recovers the old context by reading the root component's cell, seeding a
  fresh one on reload.
- **`SomeStaticComponent` now holds the component; `propsTypeOnly` is
  gone.** A static mount is the component itself bundled with its
  dictionaries, `SomeStaticComponent props context` (existential over `model`
  / `action`, `props` kept visible), rather than a `props -> SomeComponent`
  function. `VCompStatic` holds `StaticPtr (SomeStaticComponent props context)`
  next to the `props` value and the runtime builds the `SomeComponent` at
  mount time. Because the dictionaries now sit in the value a `StaticKey`
  resolves to, the Lynx main thread recovers the child's `action` and
  `props` types from the key alone, so the lazy `propsTypeOnly` placeholder
  and the "every constructor is lazy in props" invariant are gone.
  `mountStatic` now accepts components with or without `props`;
  `mountStaticWithProps` remains as a deprecated alias until 1.15. Code that
  pattern-matches the `SomeStaticComponent` constructor directly sees its
  payload change from a function to a `Component`. A new `MountConstraints`
  synonym declares the shared dictionary set once for `SomeComponent` and
  `SomeStaticComponent`. Call sites such as
  `vcomp_ (static (mountStatic comp))` are unchanged.
- **`toHtml` collapses adjacent text nodes inside fragments and across a
  `[View]`.** Previously only an element's direct children were collapsed,
  so an empty text node inside a `vfrag` rendered as a lone space that the
  client's hydration walk (which recurses into fragments) could not
  reconcile, silently falling back to a full re-render.
- **`vcontext` / `vprops` / `vmodel` children are resolved before being built.** The
  runtime now looks through these wrappers before deciding what to do with
  a child, so one that resolves to an empty fragment is skipped like an
  inline `fragment []`, and one that resolves to a plain node has its JS
  handle freed after linking like any other node. `toHtml` likewise
  resolves them before collapsing adjacent text nodes, matching the
  client's hydration-time tree.
- **Breaking: `View` gained a `props` type parameter.** `View context model
  action` is now `View context props model action`, matching the order of
  `Component context props model action`. Unlike `context`, `props` are
  per-component, so `VProps` is resolved through the type system the same
  way `model` is, rather than through a global cell. Mount combinators
  (`mount_`, `mountWithProps`, `(+>)`, `vcomp`, …) forget the child's
  `props` at the boundary exactly as they forget its `model` and `action`.
  Update signatures by inserting a `props` variable after `context`; code
  that leaves it polymorphic needs no other change. `ToHtml (View …)` and
  `ToHtml [View …]` now require `context ~ ()`, `props ~ ()` and
  `model ~ ()`: a bare `View` is static markup, and a component's view is
  rendered with `toHtmlWith` (so `toHtml (view ctx () m)` becomes
  `toHtmlWith ctx () m (view ctx () m)`). `content_` in
  `Miso.Native.X.Element.Svg.Property` now takes the `context`, `props` and
  `model` to render against, ahead of the content `View`; use `svgWith_` to
  obtain all three ambiently from the enclosing component.

### Fixed

- **`onDestroyedWith` / `onBeforeDestroyedWith` now receive the element's
  `DOMRef` instead of `undefined`.** `callDestroyed` and
  `callBeforeDestroyed` invoked the hooks with no argument, so the `DOMRef`
  callback was always `undefined` on the Haskell side. Zero-argument
  `onDestroyed` / `onBeforeDestroyed` users are unaffected.
- **Static mounts now carry their `StaticKey` as the diff key.** A
  `VCompStatic` node had no `key`, so two different `static` sites at the
  same position compared equal in the differ: the first component stayed
  mounted and the second's `diffProps` ran against it with props of an
  unrelated type. `buildComp` now sets `key` to the mount's `StaticKey`
  (unique per `static` site) when no explicit key is given, so swapping
  `vcomp_ (static (mountStatic A))` for `vcomp_ (static (mountStatic B))`
  replaces the child. Siblings built from one `static` site still diff
  positionally.

## 1.13.0.0

### Added

- **Native mobile backend.** `miso` can now target native mobile devices by
  driving the [Lynx](https://lynxjs.org) dual-thread runtime instead of the
  browser DOM. New `Miso.Native` entry point (`native` / `nativeWithContext`),
  the `Miso.Native.Element.*` element / event / property / method vocabulary,
  and main-thread event handlers for low-latency gestures. Gated behind the
  `native` cabal flag (`-fnative`); web / WASM builds are unaffected.

  Because the flag is off by default, the `Miso.Native.*` modules do not
  appear in the Hackage-generated documentation — build locally with
  `-fnative`, or see the `sample-app-native` directory for a worked example
  with iOS and Android hosts.

- **App-global `context`.** A single value shared by every `Component` in the
  tree (miso's analogue of React Context): seed with `startAppWithContext`,
  read with `getContext` (or the first argument to `view`), update with
  `modifyContext` / `modifyContext_` / `putContext`, and opt components into
  context-driven re-renders with `useContext`. `ComponentInfo` gained a
  `componentInfoContext` lens. The motivating use case is propagating
  settings such as locale or theme to every component without threading them
  through `props`.

- **Cookie Store API.** New `Miso.Cookie` module wrapping the browser's
  [CookieStore API](https://developer.mozilla.org/en-US/docs/Web/API/CookieStore)
  as `Effect` combinators — `cookieGet`, `cookieGetAll`, `cookieSet`,
  `cookieDelete`, `cookieDeleteWith`, the `Cookie` record and `defaultCookie`
  constructor, plus `_`-suffixed synchronous variants. `Miso.Subscription.Cookie`
  adds `cookieChangeSub` for subscribing to `CookieChangeEvent`s. Requires a
  secure context (HTTPS or `localhost`); on browsers without the API
  (e.g. Firefox) the error callback fires and `cookieChangeSub` is a no-op.

- **`canvasSub`.** New `Miso.Subscription.Canvas` module. `canvasSub` drives
  a `<canvas>` in a tight `requestAnimationFrame` loop, bypassing virtual DOM
  construction entirely — unlike `Miso.Canvas`, whose `draw` runs during the
  diffing process on discrete events. Pair it with `onCreatedWith` /
  `onDestroyed` and `startSub` / `stopSub` to start the loop when the canvas
  mounts and stop it on unmount. The draw callback receives each frame's
  high-resolution timestamp and a snapshot of the component's current model
  (see the `Sub` change below), and the queued frame is cancelled before the
  callback is freed on teardown.

- **`Miso.Trace`.** A browser-console analogue of `Debug.Trace` for
  debugging pure code such as `view` functions or helpers called from
  `update`. `trace`, `traceId`, `traceWith`, `traceShow`, `traceShowId`,
  `traceShowWith`, `traceM` and `traceShowM` log with `console.log`; the
  `traceWarn*` and `traceError*` families log with `console.warn` and
  `console.error` respectively, gaining the browser's severity filtering
  and stack traces. `traceTo` generalises over any `MisoString -> IO ()`
  console function from `Miso.FFI`. Like `Debug.Trace`, these are built on
  `unsafePerformIO` and are a debugging aid only.

- **Synchronous `Miso.Fetch` variants.** `_`-suffixed counterparts for the
  whole surface — `getJSON_`, `postJSON_`, `postJSON'_`, `putJSON_`,
  `getText_`, `postText_`, `putText_`, `getBlob_`, `postBlob_`, `putBlob_`,
  `getFormData_`, `postFormData_`, `putFormData_`, `getUint8Array_`,
  `postUint8Array_`, `putUint8Array_`, `getArrayBuffer_`, `postArrayBuffer_`,
  `putArrayBuffer_`, `postImage_`, `putImage_`. Each blocks the calling
  thread and returns `Either (Response error) (Response body)`. Best used
  inside `Miso.Effect.io` / `io_` so the scheduler thread is not blocked.

- **Cross-thread effects.** `runOnBG` and `runOnMain` (with the supporting
  `Thread` type) dispatch an action's `update` onto the background (BTS) or
  main (MTS) thread of the Lynx runtime. Off the native runtime, or when
  already on the target thread, both behave as an ordinary `issue`.

- **Main-thread event handlers.** `onMain` / `onMainWithOptions` in
  `Miso.Event` register handlers that run directly on the main thread, for
  low-latency gesture and animation work. `Miso.Native.MainThread` provides
  `MainThreadRef` and the imperative operations those handlers drive.
  `eventHandlerConvert` / `eventHandlerDecoder` and the `EventHandler` type
  are exported for building custom handlers.

- **Static components.** `mountStatic` and `mountStaticWithProps` mount a
  `Component` through a `StaticPtr` (`SomeStaticComponent`), so the component
  survives the dual-thread boundary; `vcomp_` / `vcomp` turn the resulting
  pointer into a `View`. Unlike the non-static combinators these need no key —
  the compile-time `StaticKey` supplies identity. To opt a statically mounted
  child into `context` re-renders, set the field directly:
  `mountStatic comp { useContext = True }`. `mountUseContext` is the
  non-static equivalent.

- **Every exported name is documented.** `cabal haddock` reported 118
  undocumented exports across 34 modules — mostly the Lynx event payloads,
  decoders, method parameter records and `Events` maps under
  `Miso.Native.Element.*`. All now carry Haddock.

- **Context-seeding SSR entry points.** `misoWithContext` and
  `prerenderWithContext` hydrate a server-rendered page with an explicit
  initial `context`; `setContext` seeds the global context for use from the
  `ToHtml` renderer. `Miso.Reload` gained matching `liveWithContext` and
  `reloadWithContext`.

- **Lynx thread detection.** `getThreads`, `onBTS` and `onMTS` in `Miso.FFI`
  report which thread the current code is executing on.

- **CSS helpers.** `transition_` builds a single shorthand `transition`
  declaration (so an imperative `transition: none` reset on the main thread
  clears it as one key), and `cubicBezier` produces a `cubic-bezier(…)`
  timing function.

- **`Miso.DSL` additions.** `await` for awaiting a JS promise from Haskell,
  and the `JSException` type (which now has an `Exception` instance).

- **`DirectEvents`.** `VNode` carries a set of directly-dispatched events,
  readable via `nodeDirectEvents`, used by the native runtime to skip the
  scratch-node round trip.

- **Types that were reachable but not exported.** Several types appeared in
  exported signatures without being exported themselves, so callers could not
  name them: `Consumed` (the payload of `Miso.Native.Element.List.Method`'s
  callback), `GetTextBoundingRect` (the parameter of `getTextBoundingRect`),
  `ListItemInfo`, `AnimationType` and `UIAppearanceDetailEventType` (field
  types of exported Lynx event records), and `ComponentIds` (the type of
  `ComponentState`'s `_componentChildren`). `Miso.JSON` now exports `ToJSON`
  with both methods — `toJSONList` was hidden, so it could not be overridden
  outside the module — along with the four generic-deriving classes missing
  from its `Generics` group (`GToJSONRep`, `GToJSONSumNullary`,
  `GFromJSONRep`, `GFromJSONSumNullary`). `Miso.Lens.Generic` likewise exports
  the type-level machinery its `HasLens` instances mention (`GSet`,
  `GetFieldType`, `TotalityCheck`, `And`, `Or`).

- **`aeson` cabal flag.** When enabled (`-faeson`, off by default),
  `Miso.JSON` keeps its API but is defined in terms of
  [aeson](https://hackage.haskell.org/package/aeson): `Value`, `Object`, and
  `Parser` become aeson's types, so existing aeson `ToJSON` / `FromJSON`
  instances work directly with `Miso.Fetch`, `Miso.WebSocket`, and the event
  decoders. Signatures are unchanged — the accessors still take `MisoString`
  keys, `withArray` still passes the continuation a `[Value]`, `withNumber`
  still passes a `Double`, and `Result` still carries `MisoString` error
  messages. On the JS / WASM backends orphan instances make `JSString` a
  first-class JSON citizen. Miso's own generic-deriving machinery (`GToJSON`
  et al.) is not exported in this mode; aeson's `genericToJSON` / `Options` /
  `camelTo2` are re-exported instead. CI runs the WASM integration suite in
  both modes.

- **`text` cabal flag on WASM.** When enabled (`-ftext`, off by default),
  `MisoString` is `Data.Text.Text` instead of `JSString` on the WASM
  backend too (previously this was only possible on the `VANILLA` / SSR
  build). `Data.JSString` remains the FFI boundary type, so DOM writes
  still convert `Text -> JSString` on the way out. Number formatting and
  parsing take advantage of this to avoid unnecessary FFI round trips:
  `toMisoString` on `Int` / `Word` / `Double` / `Float` builds `Text`
  directly via `Data.Text.Lazy.Builder` (`decimal` / `realFloat`) instead
  of allocating a throwaway `JSVal` via JS's `.toString()`, since GHC's
  `Show` formatting is what these functions target on this backend
  regardless. Likewise,
  `fromMisoString` on `Int` / `Word` / `Double` / `Float` parses directly
  with `Data.Text.Read` instead of round-tripping through
  `JSString`/`parseInt`/`parseFloat`, while reproducing the JS parsers'
  semantics: leading/trailing whitespace and trailing garbage are
  ignored, a leading `+`/`-` is accepted, and integers with a `0x`/`0X`
  prefix parse as hexadecimal. CI gained a `playwright-wasm-aeson-text`
  target that runs the WASM integration suite with both the `aeson` and
  `text` flags enabled together.

### Changed

- **Breaking: `View` and `Attribute` gained type parameters.**
  `View context model action` and `Attribute model action`. This lets event
  handlers read the current `model` and supports the native dual-thread
  `static` handler protocol. `VNode` now carries a `DirectEvents` set, the
  key moved into `SomeComponent (Maybe Key) …`, and `VComp` / `VCompStatic` /
  `SomeStaticComponent` were restructured. Downstream `view` and attribute
  signatures must be updated accordingly.

- **Breaking: `Sub` gained a `model` type parameter.** `Sub action` is now
  `Sub model action`, and a subscription receives a second argument — an
  `IO model` that returns a snapshot of the component's current model:
  `type Sub model action = Sink action -> IO model -> IO ()`. This lets
  long-running subscriptions (like `canvasSub`) read the latest model
  without threading it through actions. All bundled subscriptions were
  updated; user-defined subscriptions that ignore the model need only accept
  (and discard) the extra argument, e.g.
  `tickSub sink _ = forever (threadDelay delay >> sink Tick)`. `mapSub`,
  `createSub`, and `startSub` were updated accordingly.

- **Breaking: `Miso.Binding` was removed.** The experimental lens-based
  parent/child model synchronisation mechanism (`Binding`, `Bindings`,
  `Precedence`, and the `bindings` field on `Component`) is gone, along with
  its propagation phase in the scheduler. Use the new app-global `context`
  for shared state, or asynchronous messaging via `broadcast` / `Miso.PubSub`
  for point-to-point communication.

- **Breaking: `parent` and `ROOT` were removed.** `Component` no longer
  carries a `parent`; the `ROOT` marker that demarcated the top of the page
  is unnecessary without it. Both are superseded by `context`.

- **Breaking: `Miso.Types.keyed` was removed.** Use the keyed constructors
  directly: `textKey` / `textKey_` for text, `fragment_` / `vfrag_` for
  fragments, `mount_` / `vcomp_` / `mountStatic` for components, and
  `key_` in the attribute list for element nodes.

- **Breaking: runtime internals dropped from `Miso.FFI`.** `mountComponent`,
  `unmountComponent` and `modelHydration` (and `getComponentContext` from
  `Miso.FFI.Internal`) were documented as runtime-use-only and have been
  removed as part of the dual-thread rework. They have no user-facing
  replacement.

- **Breaking: `autocomplete_` takes a `MisoString`.** It was
  `Bool -> Attribute action`, which could only produce `"on"` / `"off"` and
  could not express the many other valid values (`"email"`, `"new-password"`,
  …). It is now `MisoString -> Attribute action`; replace `autocomplete_ True`
  with `autocomplete_ "on"`.

- **Breaking: `Miso.Util.Parser.endOfInput` was generalised** from
  `Parser a ()` to `ParserT r [a] [] ()`. Call sites are unaffected unless
  they carried an explicit type annotation.

- **`MisoString` `length` and `take` are code-point based on WASM.** They
  previously counted UTF-16 code units, so a string holding a single
  astral-plane character (an emoji, say) reported a length of 2. They now
  agree with `Data.Text` and with the GHCJS backend. Only the WASM backend
  was affected.

- **`context` no longer requires `ToJSON` / `FromJSON`.** The constraints
  were unused — `context` is never sent across the dual-thread boundary.

### Removed

- **`Miso.String.QQ`.** The `misoString` QuasiQuoter for multiline
  `MisoString` literals is gone. GHC's `MultilineStrings` extension
  (GHC 9.12+) covers the use case directly — enable the pragma and write
  triple-quoted `MisoString` literals. (`Miso.FFI.QQ` and `Miso.Lens.TH`,
  the other `template-haskell`-flag modules, are unaffected.)

### Fixed

- **`Miso.Fetch`'s `none` response type no longer double-fires the success
  callback.** `fetchCore` called the success callback directly for
  `responseType == "none"` and then fell through into a second, unconditional
  `.then` that called it again with `body: undefined`. Every `post*`/`put*`
  variant that discards the response body (`postJSON`, `postJSON_`, `putText`,
  `putBlob_`, etc.) dispatched its success action twice per request.

- **Native: attribute removal actually removes the attribute.** The MTS
  drawing context's `removeAttribute` called `__SetAttribute(node, key, '')`.
  The engine's `Element::SetAttribute` (`lynx/core/renderer/dom/element.cc`)
  only takes the removal branch when the value is lepus-empty
  (`null`/`undefined`) — an empty string is an ordinary string value, so it
  was stored in `updated_attr_map_` instead of being removed. Every prop
  diffed off a native element (`dom.ts`'s `diffProps`, which routes native
  removals through this path) was setting it to `''` rather than clearing
  it. Now passes `null`.

- **`rAFSub` now cancels the pending animation frame on unsubscribe.**
  Release freed the `requestAnimationFrame` callback without cancelling the
  frame already queued in the browser; the next frame then invoked a freed
  callback and crashed the WASM RTS with `internal error: stg_ap_p_ret`.
  `Miso.Canvas`'s `draw` also moved from a `syncCallback` to an
  `asyncCallback`, fixing a `schedule: re-entered unsafely` crash when a
  component unmounted mid-diff.

- **Non-bubbling media events are registered in the capture phase.**
  `durationchange`, `loadeddata`, `loadedmetadata` and `loadstart` do not
  bubble, so their delegated listeners — registered in the bubble phase —
  never received them and `onLoadedMetadata` and friends silently never
  fired. They are now registered with capture, like the other non-bubbling
  entries in `mediaEvents`.

- **Native: the layout custom event is recognised under its released name.**
  Released Lynx engines (e.g. LynxExplorer apps) emit it as `layout`, while
  newer Lynx sources emit `layoutchange`; miso only listened for the latter,
  so `onLayoutChange` never fired on released engines. `onLayout` /
  `onLayoutMainWith` are added as aliases so apps can bind both when the
  host engine version is unknown.

- **Native: `consumeSlideEvent_` sends the shape Lynx expects.** Lynx parses
  `consume-slide-event` as `[start, end]` angle-range pairs (degrees,
  -180..180), but the binding serialised a flat list of angles instead of
  paired ranges, a shape the engine silently ignores.

- **`autocorrect_` and `spellcheck_` wrote to the wrong attribute.** Both
  emitted `autocomplete` instead of their own attribute name. `spellcheck_`
  additionally now emits `"true"` / `"false"` rather than `"on"` / `"off"`.

- **`MOUNT` errors on a missing `domRef`** instead of synthesizing a bogus
  parent node and failing later in the diff.

- **Key-based model recovery is gated on `liveMode`,** so a component no
  longer reuses an unrelated model outside of hot reload.

- **`pendingStaticKey` / `pendingMainThread` are reset before plain `On`
  handlers run,** preventing state from one handler leaking into the next.

- **`-fssr` compiles together with `-fnative`.**

- **`JSException` derives `Exception`,** so it can be `throw`n and `catch`ed
  normally.

- **Non-bubbling `mouseleave`/`pointerleave` are registered in the capture
  phase.** Neither event bubbles per the DOM spec (unlike `mouseout` /
  `pointerout`, which correctly bubble), but the delegated listener was
  registered in the bubble phase, so `onMouseLeave` / `onPointerLeave`
  handlers on any non-root element silently never fired. Same bug class as
  the non-bubbling media events fix above, extended to these two.

- **`vcomp` was misused as a synonym for `component` in `Miso.hs`'s
  documentation.** `vcomp` builds a `VCompStatic` from a `StaticPtr` (the
  static-component feature), not a `Component` from `model` / `update` /
  `view` functions. The module's own "Your first Component" example and two
  other doc snippets used `vcomp` where `component` was meant, so copying
  them verbatim would not typecheck.

- **`MisoString`'s `drop` is code-point-based on WASM, matching `take` /
  `length`.** `take` / `length` were made code-point-based to fix
  astral-character (e.g. emoji) miscounting, but `drop` was left on raw
  UTF-16 slicing. Since `splitAt` is defined as `(take n xs, drop n xs)`,
  the two disagreed on where position `n` falls for any string containing
  an astral character before it, corrupting the split.

- **`-ftext` `parseInt` mis-parses negative hex.** `"-0x1A"` checked for a
  `0x` / `0X` prefix before stripping a sign, so it never matched and fell
  through to a decimal parse of `"0x1A"`, silently returning `0` instead of
  `-26`. The sign is now stripped first, then the remainder is checked for
  a hex prefix.

- **`eventJSON` decodes a null/undefined path as `null` instead of
  crashing.** A decoder path landing on `null`/`undefined` — `relatedTarget`,
  `currentTarget`, `form`, `list`, etc. are all legitimately null/undefined
  on many real DOM events — hit `'length' in obj` on the nullish value and
  threw `TypeError`, crashing event dispatch instead of decoding the field
  as `null`. An intermediate nullish step one segment earlier had the same
  problem; both are now handled.
  
- **`freeLifecycleHooks` frees a component's `mount`/`unmount` callbacks
  again.** It read the `mount`/`unmount` fields off the component's own
  rendered content root instead of the `VComp` wrapper node that actually
  holds them (reachable one hop up, via the content root's `parent` link),
  so `fromJSVal` always failed and `freeFunction` was never called. Every
  non-root `Component` unmount — normal teardown and every GHCi hot-reload
  cycle — leaked the closures `mountCallback`/`unmountCallback` capture,
  which includes the whole `initialize` closure (`app`, `events`, `sink`,
  `model`). See Note [Freeing event handler callbacks] in `Miso.Runtime`.

### Performance

- **Short-lived `JSVal` handles are freed eagerly in the WASM runtime.**
  On the WASM backend every `JSVal` carries a weak pointer with a C
  finalizer, and the RTS copies all of them at every GC — so the hundreds of
  scratch handles `buildVTree` allocates per frame made GC pauses scale with
  handle churn (~100 ms pauses with ~50 KB of live data in profiling). The
  runtime now releases handles nothing else can reach via the new
  `Miso.DSL.freeJSVal` (`GHC.Wasm.Prim.freeJSVal` on WASM, a no-op on other
  backends), and event handler callbacks are freed when their vtree is
  replaced. Measured on miso-mario, `C_FINALIZER_LIST` copied per GC dropped
  from 7.3 MB to 2.3 MB. See Note [Freeing VTree handles] in `Miso.Runtime`.

- **`StableName` dirty-checking extended to `context` and `props`.**
  `modelCheck` was generalised to `dirtyCheck :: Eq a => a -> a -> Bool` and
  applied to the remaining sites that performed a full structural `Eq` walk
  on every check. The common case — two reads of the same `IORef` returning
  the same heap object — now short-circuits on pointer equality, which
  matters most for large contexts such as i18n translation maps.

- **Main-thread events dispatch directly,** with no scratch-node or JS
  round trip.

- **The thread environment (`mts` / `bts` / `web`) is cached** as a static
  global in the runtime rather than re-queried on every `initialize` /
  `initComponent`.