# Changelog
## 0.1.0-alpha.2 — 2026-06-11
### Added
- **`mix dstar.https`** — one-command trusted HTTPS for dev. Adds a
`my-app.test` entry to `/etc/hosts` and generates a browser-trusted
certificate via mkcert, enabling HTTP/2 so SSE streams don't exhaust the
browser's 6-connection HTTP/1.1 limit. Tries cached `sudo -n` first,
falls back to a GUI prompt on macOS (`osascript`) and interactive sudo
on Linux; detects conflicting hosts entries before touching anything.
Supports `--host`, `--cert`, `--key`, `--ip`, `--dry-run`, and `--yes`.
### Fixed
- **`Dstar.Page.Plug`'s stream loop no longer consumes Bandit's HTTP/2
flow-control messages.** Over HTTP/2, Bandit runs each stream in its own
process and delivers `{:bandit, {:send_window_update, _}}` (and friends)
to that process's mailbox, consuming them by selective receive inside
its send path. The library loop's catch-all `receive` stole those
messages, logging them as unhandled page messages and — worse —
discarding the window credit, which would stall the stream with a
flow-control timeout once the send window drained. The loop now skips
`{:bandit, _}` messages so Bandit finds them where it expects them.
- **`Dstar.Page.Plug` now loads the page module before probing its
callbacks.** `function_exported?/3` returns `false` for modules the code
server has not loaded yet, so under lazy code loading (dev and test on a
fresh VM) the first GET to a page silently skipped `mount/2` — typically
crashing `render/1` with a `KeyError` — and the first stream POST
returned 404 despite a defined `handle_connect/2`. The plug now calls
`Code.ensure_loaded?/1` before `function_exported?/3`.
## 0.1.0-alpha.1
The unified page module release. A Datastar page is now one module and
one router line. Alpha until the page layer has survived its proving
ground (a full production-app migration); the functional core is the
same battle-tested code as 0.0.x.
### Added
- `Dstar.Page` — `use` it for one-module pages: `mount/2`, `render/1`,
`handle_event/3`, `handle_connect/2`, `handle_info/2`, `stream_key/1`.
- `Dstar.Page.Plug` — drives all page requests; owns the SSE receive
loop with idle checks and stray-message tolerance.
- `Dstar.Component` — shared UI with colocated event handlers; `event/2`
targets the dispatch URL with a client-side `data-ds-prefix` base.
- `Dstar.Router` — `dstar/2` (page routes) and `dstar_components/2`
(dispatch route) macros.
- `Dstar.Page.Helpers` — `event/1,2`, `connect/0,1`, `patch/3,4`.
- `Dstar.Page.Assigns` — `assign`/`assign_new`/`update` working on both
conns and component assigns.
- `Dstar.Test` — `sse_events/1`, `patched_signals/1`,
`assert_patched_signals/2`, `assert_patched_element/2`.
- Optional deps: `phoenix ~> 1.7`, `phoenix_live_view ~> 1.0`. The
functional core still needs only `plug` + `jason`.
### Changed
- Docs restructured around pages; the original API is now "the
functional core". Nothing breaks: all 0.0.x code works unchanged.
## 0.0.10 — 2026-04-24
### Fixed
- **Removed `Connection: keep-alive` header from SSE responses.** The header
is forbidden in HTTP/2 (RFC 9113 §8.2.2) and caused browsers and curl to
reject the entire response body over HTTP/2 connections. It was also
redundant in HTTP/1.1, where keep-alive is already the default.
## 0.0.9 — 2026-04-16
### Fixed
- **README copy fixes.** The install snippet now references `~> 0.0.9`
(was stuck on `~> 0.0.7` in the v0.0.8 release). Removed two stale
mentions of "CSRF headers" in the URL-generation feature list and the
Quick Start — verb helpers stopped injecting CSRF headers in 0.0.8
when CSRF moved to the Phoenix meta tag. Removed a stray `T` after the
License section.
## 0.0.8 — 2026-04-16
Consolidates the unreleased 0.0.7 work (CSRF rewrite, expanded usage rules)
with Datastar v1.0 compatibility fixes. Users on 0.0.6 upgrading to 0.0.8
should read the **Changed** section — CSRF handling has been rewritten.
### Changed
- **CSRF is no longer transported through a Datastar signal.** Verb helpers
(`Dstar.post/2,3`, `get`, `put`, `patch`, `delete`) no longer inject an
`{headers: {'x-csrf-token': $_csrfToken}}` options object into generated
expressions. Datastar reads the token from Phoenix's standard
`<meta name="csrf-token">` tag and sends it as an `x-csrf-token` header
automatically. This decouples CSRF from Datastar's signal round-tripping
and shortens every generated expression.
**Migration from 0.0.6:** remove any `data-signals:_csrf-token` /
`$_csrfToken` signal from your root layout. Keep the standard Phoenix
`<meta name="csrf-token" content={get_csrf_token()}>` tag in `<head>`.
If you use Datastar-driven form POSTs that go through `Plug.CSRFProtection`,
expose the token as a **non-prefixed** `csrf` signal and keep
`Dstar.Plugs.RenameCsrfParam` in your pipeline — that plug's role is now
scoped to bridging form posts, not SSE.
- **Datastar version references bumped from `1.0.0-RC.8` to `v1.0.0`** in
the README, `doc/readme.md`, and the `datastar-attributes` usage rule.
v1.0's other changes (new `data-bind` `__prop`/`__event` modifiers,
`data-on` `__document` modifier, morphing improvements,
`retryMaxWaitMs` → `retryMaxWait` rename, Rocket JS API rewrite) are all
client-side and need no Dstar changes.
### Fixed
- **`Dstar.Signals.read/1` now reads signals from query params for DELETE
requests.** Datastar v1.0 stopped sending a body on DELETE requests
([#1144](https://github.com/starfederation/datastar/issues/1144)), so
signals from `Dstar.delete/2,3` actions arrived empty under the previous
code path. `read/1` now treats GET and DELETE the same — both read from
the `datastar` query param.
### Added
- **New usage-rules reference files** ship with the package: `error-handling.md`,
`heex-rendering.md`, `loading-states.md`, and a full `datastar-attributes.md`
cheat sheet. Consumers of the `usage_rules` package will pick these up
automatically.
- **HTTP/2 SSE connection limit docs** added to the README, covering why the
browser 6-connection-per-origin cap on HTTP/1.1 matters for long-lived
Datastar streams and how HTTP/2 multiplexing removes it.
## 0.0.6 — 2026-03-21
### Added
- **`Dstar.Utility.StreamRegistry`** — Opt-in per-tab SSE stream
deduplication. Tracks one stream process per user+tab using Elixir's
`Registry`. When a new stream opens from the same tab, the previous process
is killed instantly — no waiting for keepalive timeouts or PubSub broadcasts.
Fixes zombie processes that hold subscriptions, run wasted DB queries, and
exhaust the browser's 6-connection-per-origin limit on HTTP/1.1. Add it to
your supervision tree, set a `tabId` signal in your root layout, and replace
`Dstar.start/1` with `Dstar.start_stream/2`. Falls back to `Dstar.start/1`
when no `tabId` is present. See the README's "Stream Deduplication" section
for full setup.
- **`Dstar.start_stream/2`** — Convenience delegate to
`Dstar.Utility.StreamRegistry.start_stream/2`.
## 0.0.5 — 2026-03-18
### Added
- **`Dstar.SSE.check_connection/1`** — Checks if an SSE connection is still
open by sending an SSE comment line. Returns `{:ok, conn}` if the connection
is active, `{:error, conn}` if closed or not yet started. Useful for
detecting disconnections in streaming loops. Also available via
`Dstar.check_connection/1`.
- **`Dstar.Signals.remove_signals/3`** — Removes signals from the client by
setting them to `nil`. Accepts a single dot-notated path string (e.g.
`"user.profile.theme"`) or a list of paths (e.g. `["user.name",
"user.email"]`). Paths with shared prefixes are deep-merged correctly:
`["user.a", "user.b"]` becomes `%{"user" => %{"a" => nil, "b" => nil}}`.
Validates paths and raises on empty strings, leading/trailing/consecutive
dots. Also available via `Dstar.remove_signals/3` and
`Dstar.Signals.format_remove/2` for string formatting.
- **`:namespace` option for `Dstar.Elements.patch/3` and
`Dstar.Elements.format_patch/2`** — Specify element namespace: `:html`
(default), `:svg`, or `:mathml`. When set to `:svg` or `:mathml`, emits a
`namespace` data line in the SSE event. Default `:html` omits the line
(backward compatible).
### Changed
- **`Dstar.Elements.patch/3` and `Dstar.Elements.format_patch/2` now accept
`Phoenix.HTML.safe()` tuples** in addition to plain binary strings. Pass
HEEx template output and `Phoenix.HTML` helpers directly without manual
conversion.
- **`Dstar.Scripts.redirect/3` now uses `Jason.encode!/1`** for URL encoding
instead of manual JavaScript string escaping. Prevents injection via special
characters, `</script>` sequences, and Unicode. Generated JS changed from
`window.location='url'` to `window.location.href="url"`.
## 0.0.4 — 2026-03-15
### Added
- **HTTP verb helpers** — `Dstar.post/2,3`, `Dstar.get/2,3`, `Dstar.put/2,3`,
`Dstar.patch/2,3`, `Dstar.delete/2,3` generate `@verb(...)` expressions for
Datastar attributes. Same API across all verbs.
### Deprecated
- `Dstar.event/2,3` — use `Dstar.post/2,3` (or the appropriate verb) instead.
Still works, will be removed in a future version.
## 0.0.3 — 2026-03-15
### Added
- **UsageRules integration** — ships `usage-rules.md`, streaming sub-rule, and
a pre-built `use-dstar` skill with API patterns reference. Consumers using
the `usage_rules` package can pull these in automatically.
### Changed
- **README rewrite** — new Quick Start walks through routes → controller →
event handler → template, showing `patch_signals`, `patch_elements`,
`execute_script`, and `console_log` in one cohesive counter example.
Dispatch is now the primary routing pattern; plain controller routes shown
as an alternative in "Without Dispatch" section.
## 0.0.2 — 2026-03-15
### Added
- Migration guide from PhoenixDatastar to Dstar (`docs/migrating-from-phoenix-datastar.md`)
## 0.0.1 — 2025-03-12
Initial release after the grug-brain simplification. The library was gutted from
1,742 lines / 15 files down to ~735 lines / 6 files. Everything that
reimplemented LiveView was deleted.
### What's in the box
- **`Dstar.SSE`** — Open SSE connections (`start/1`), send events
(`send_event/4`, `send_event!/4`), format events as strings
(`format_event/2`).
- **`Dstar.Signals`** — Read Datastar signals from requests (`read/1`). Patch
signals on the client via SSE (`patch/3`). Format signal patches as strings
(`format_patch/2`).
- **`Dstar.Elements`** — Patch DOM elements via SSE (`patch/3`) with selector,
mode, and view transition support. Remove elements (`remove/3`). Format
patches as strings (`format_patch/2`).
- **`Dstar.Actions`** — Generate `@post(...)` expressions for Datastar
attributes (`event/1,2`). Encode/decode Elixir module names to/from
URL-safe strings (`encode_module/1`, `decode_module/1`).
- **`Dstar.Plugs.Dispatch`** — Optional dynamic dispatch plug. Routes
`POST /ds/:module/:event` to handler modules from an allowlist.
- **`Dstar`** — Thin convenience module that delegates to the above.
### What was removed
The following modules were deleted because they reimplemented LiveView's
process-per-session model, which contradicts Datastar's client-holds-state
architecture:
- `Dstar.Server` — GenServer per session (284 lines)
- `Dstar.Socket` — LiveView-style socket struct with event queues (315 lines)
- `Dstar.Plugs.Page` — Custom HTML page renderer (147 lines)
- `Dstar.Plugs.Stream` — Token-verified SSE streaming endpoint (80 lines)
- `Dstar.Scripts` — Script execution via DOM patching (106 lines)
- `Dstar.Registry` — Process registry for GenServers (28 lines)
- `Dstar.Token` — Plug.Crypto token signing (48 lines)
- `Dstar.Application` — OTP application that started the registry (15 lines)
- `Dstar.Helpers.JS` — JS string escaping helper (13 lines)
- `Dstar` behaviour and `__using__` macro — mount/handle_event/render callback
system (rewritten to thin delegation module)
### Design
- No processes. No supervision tree. No OTP application callback.
- No behaviours. No macros. No `use Dstar`.
- Two dependencies: `plug` and `jason`.
- Designed to sit on top of deadview Phoenix. Use your controllers, templates,
and layouts. The library just formats and sends SSE events.