Co-authored with @ethan-piper. Consolidates the on-screen dice roller + the hardening follow-ups (#47 / #48 / #49) plus the release-cut completion in #51.
What's in 2.1.4
On-screen dice roller (#48)
A fifth Dice launcher on the editorial rail slides out a drawer hosting the same `#dice-pad` players use on their phones — reel, die grid, advantage, modifier, label, prescribed-roll prefill — reused 1:1, costs zero screen space when closed.
Under `roll_mode: players`, the DM prescribes a roll via `send.py --dice-request … --wait`; the server tracks which characters have a live phone bound (`/stream?character=`) and routes the roll there if so, or auto-opens the on-screen drawer if the target has no phone. A Settings "Roll on screen" toggle forces on-screen rolling regardless.
Rolls go through the existing `POST /player-input/dice` (server-authoritative, resolves the request, releases the DM's `--wait`). Editorial-skin only; opt-in.
Pref endpoint hardening (#47)
`/narration-pref` and `/roll-pref` now sit behind the same `_token_ok()` + rate-limit gate as every other write endpoint (they previously accepted any LAN request). `/roll-pref` additionally validates the `character` field with `_char_ok()` — it gets templated into a `[[ roll mode: …]]` directive in the DM's prompt, so an unsanitized value was a prompt-injection surface.
Editorial NPC dialogue size fix (#51)
NPC pull-quotes dropped from `1.5em` to `1.2em` (line-height `1.42`) — the previous 1.5× jump over body text was overlarge and overflowed the reading column.
Internal hardening (#49)
Defensive `_prefill` guard + dev `console.warn` around the TV-view dice-routing wrap, and unit tests for `_phone_present()` (routing-presence semantics).
Update
```
/plugin update dm
```
then `/reload-plugins` (or restart Claude Code) to pick it up. As of #51 the marketplace correctly advertises 2.1.4, so this serves the new code.