diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..c66283c --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,69 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +Entries for unreleased work are not written here directly. Each issue drops a +fragment in [`changelog.d/`](changelog.d/README.md); the fragments are assembled +into a version section at release. See that README for the format and for when a +change warrants an entry at all. + +## [0.1.0] 2026-08-22 + +First release: authoring, observing, and debugging components for the +[statifier](https://hex.pm/packages/statifier) statechart engine, consuming +its effect stream through the language-neutral trace wire format (format +version 1). The Livebook inspector is the first assembled frontend; the +panes underneath it are pure folds any other frontend can render. + +### Added + +- `StatifierUI.Diagram.render/2` renders a compiled machine and an active + configuration as Mermaid `stateDiagram-v2` source for `Kino.Mermaid`, with + composite nesting, parallel regions, active-state highlighting, and + cross-hierarchy transitions lifted to the composite level with a + `[lifted: ...]` marker. +- `StatifierUI.EventLog.build/1` folds a trace message stream into a log + grouped by `(macrostep, round)`, ordered by the producer's stamps rather + than arrival, and `StatifierUI.EventLog.Markdown.render/2` renders it as + collapsible Markdown for `Kino.Markdown`, with wire-format indexes + resolved to state and transition names by `StatifierUI.EventLog.Labels`. +- `StatifierUI.EventInjection.build/1` turns an ADR-0003 fixture bundle + (or `nil`) into the event-injection pane model: a sorted palette of + editable event buttons via `StatifierUI.EventInjection.Palette`, a + `free_form_only?` flag for the fixture-less degraded mode, and + `send/2`/`send_draft/3` to deliver a `StatifierUI.EventInjection.Draft` + through `Statifier.Session.send_event/2` - the ordinary recordable input + path, per statifier ADR-0029. +- `StatifierUI.DatamodelExplorer.build_authoring/3` and `build_live/2` build + a read-only datamodel tree - document `` declarations, spec + 5.10.1 system variables, predicator provider functions in scope, and + either a fixture scenario or a live session's datamodel with entries + marked `changed?` per macrostep - and + `StatifierUI.DatamodelExplorer.Markdown.render/2` renders it as Markdown + for `Kino.Markdown`. +- `StatifierUI.Kino.inspect/3` assembles the Livebook inspector: the + configuration diagram, datamodel explorer, event injection, and event + log panes composed over one shared subscriber, live-updating, detaching + cleanly on cell re-evaluation. Compiled only when the optional `:kino` + dependency is present. +- `StatifierUI.Trace.Subscriber.attach/3` accepts `catch_up: true`: on a + session started with `record: true` the missed prefix is replayed into + the buffer atomically with the subscription (statifier ADR-0049); an + unrecorded session falls back to live delivery with a `:not_recorded` + diagnostic the inspector surfaces as "Live-only". +- `StatifierUI.Inspector` - the pure pane-assembly fold the Kino shell + renders, usable by any other frontend. +- `notebooks/inspector.livemd` - the demo notebook, doubling as the + milestone's manual acceptance test. +- Serializes statifier's `DatamodelChange` effect as the + `effect.datamodel_change` wire type, so consumers can observe datamodel + values as they are written instead of only the variable names + `session.datamodel` carries. New types are additive under the wire + format's must-ignore rule. +- Every `effect.*` wire message carries the engine's `round` stamp in + its envelope, alongside `macrostep` and `microstep`; consumers reading + older recorded streams must still tolerate `effect.*` messages without + the key. diff --git a/README.md b/README.md index 46e4993..72d7235 100644 --- a/README.md +++ b/README.md @@ -15,10 +15,22 @@ stamps them with `(macrostep, round)` counters, and retains source locations on states, transitions, and expressions. A UI is one more interpreter of those effects; the engine needs nothing changed to support it. +## Installation + +```elixir +def deps do + [ + {:statifier_ui, "~> 0.1"} + ] +end +``` + +The `:kino` (Livebook) and `:phoenix_live_view` integrations are optional +dependencies - add whichever your host actually renders with. + ## Status -Early. Nothing is published to hex yet, and the engine dependency is a git dep -until statifier publishes. The first milestone is the Livebook inspector: +Early. The first milestone is the Livebook inspector: `StatifierUI.Kino.inspect/3` over a running `Statifier.Session` - [`notebooks/inspector.livemd`](notebooks/inspector.livemd) walks it end to end. diff --git a/changelog.d/sui-67d.md b/changelog.d/sui-67d.md deleted file mode 100644 index ce3567b..0000000 --- a/changelog.d/sui-67d.md +++ /dev/null @@ -1,8 +0,0 @@ -### Added - -- Every `effect.*` wire message now carries the engine's `round` stamp in - its envelope, alongside `macrostep` and `microstep` - - `effect.budget_exhausted` already carried it, and the other nine types - gain it. An additive field under the wire format's must-ignore rule, so - the format version stays 1; consumers reading older recorded streams - must still tolerate `effect.*` messages without the key. diff --git a/changelog.d/sui-h92.md b/changelog.d/sui-h92.md deleted file mode 100644 index 97dbb48..0000000 --- a/changelog.d/sui-h92.md +++ /dev/null @@ -1,7 +0,0 @@ -### Added - -- Serializes statifier's `DatamodelChange` effect as the new - `effect.datamodel_change` wire type, so consumers can observe datamodel - values as they are written instead of only the variable names - `session.datamodel` carries. The format version stays 1; new types are - additive under the wire format's must-ignore rule. diff --git a/changelog.d/sui-t36.4.md b/changelog.d/sui-t36.4.md deleted file mode 100644 index afbca02..0000000 --- a/changelog.d/sui-t36.4.md +++ /dev/null @@ -1,7 +0,0 @@ -### Added - -- `StatifierUI.Diagram.render/2` renders a compiled machine and an active - configuration as Mermaid `stateDiagram-v2` source for `Kino.Mermaid`, with - composite nesting, parallel regions, active-state highlighting, and - cross-hierarchy transitions lifted to the composite level with a - `[lifted: ...]` marker. diff --git a/changelog.d/sui-t36.5.md b/changelog.d/sui-t36.5.md deleted file mode 100644 index 3247c5a..0000000 --- a/changelog.d/sui-t36.5.md +++ /dev/null @@ -1,8 +0,0 @@ -### Added - -- `StatifierUI.EventLog.build/1` folds a trace message stream into a log - grouped by `(macrostep, round)`, ordered by the producer's stamps rather - than arrival, and `StatifierUI.EventLog.Markdown.render/2` renders it as - collapsible Markdown for `Kino.Markdown`, with wire-format indexes - resolved to state and transition names by - `StatifierUI.EventLog.Labels`. diff --git a/changelog.d/sui-t36.6.md b/changelog.d/sui-t36.6.md deleted file mode 100644 index c923472..0000000 --- a/changelog.d/sui-t36.6.md +++ /dev/null @@ -1,9 +0,0 @@ -### Added - -- `StatifierUI.EventInjection.build/1` turns an `ADR-0003` fixture bundle - (or `nil`) into the event-injection pane model: a sorted palette of - editable event buttons via `StatifierUI.EventInjection.Palette`, a - `free_form_only?` flag for the fixture-less degraded mode, and - `send/2`/`send_draft/3` to deliver a `StatifierUI.EventInjection.Draft` - through `Statifier.Session.send_event/2` - the ordinary recordable input - path, per statifier ADR-0029. diff --git a/changelog.d/sui-t36.7.md b/changelog.d/sui-t36.7.md deleted file mode 100644 index 5f60979..0000000 --- a/changelog.d/sui-t36.7.md +++ /dev/null @@ -1,9 +0,0 @@ -### Added - -- `StatifierUI.DatamodelExplorer.build_authoring/3` and `build_live/2` build - a read-only datamodel tree - document `` declarations, spec - 5.10.1 system variables, predicator provider functions in scope, and - either a fixture scenario or a live session's datamodel with entries - marked `changed?` per macrostep - and - `StatifierUI.DatamodelExplorer.Markdown.render/2` renders it as Markdown - for `Kino.Markdown`. diff --git a/changelog.d/sui-t36.8.md b/changelog.d/sui-t36.8.md deleted file mode 100644 index db35b66..0000000 --- a/changelog.d/sui-t36.8.md +++ /dev/null @@ -1,16 +0,0 @@ -### Added - -- `StatifierUI.Kino.inspect/3` assembles the Livebook inspector: the - configuration diagram, datamodel explorer, event injection, and event - log panes composed over one shared subscriber, live-updating, detaching - cleanly on cell re-evaluation. Compiled only when the optional `:kino` - dependency is present. -- `StatifierUI.Trace.Subscriber.attach/3` accepts `catch_up: true`: on a - session started with `record: true` the missed prefix is replayed into - the buffer atomically with the subscription (statifier ADR-0049); an - unrecorded session falls back to live delivery with a `:not_recorded` - diagnostic the inspector surfaces as "Live-only". -- `StatifierUI.Inspector` - the pure pane-assembly fold the Kino shell - renders, usable by any other frontend. -- `notebooks/inspector.livemd` - the demo notebook, doubling as the - milestone's manual acceptance test. diff --git a/mix.exs b/mix.exs index f6d2680..d1304d7 100644 --- a/mix.exs +++ b/mix.exs @@ -1,7 +1,7 @@ defmodule StatifierUI.MixProject do use Mix.Project - @version "0.1.0-dev" + @version "0.1.0" @source_url "https://github.com/riddler/statifier-ui" def project do @@ -15,6 +15,8 @@ defmodule StatifierUI.MixProject do name: "StatifierUI", description: "UI components for authoring, observing, and debugging statifier statecharts", source_url: @source_url, + docs: docs(), + package: package(), test_coverage: [tool: ExCoveralls], preferred_cli_env: [ coveralls: :test, @@ -33,12 +35,47 @@ defmodule StatifierUI.MixProject do defp elixirc_paths(:test), do: ["lib", "test/support"] defp elixirc_paths(_env), do: ["lib"] + # Hexdocs configuration. These paths are read off the publisher's disk at + # `mix docs` time and need no entry in package()'s files: list - the docs + # tarball hexdocs hosts is built separately from the package tarball + # `mix deps.get` fetches. + defp docs do + [ + name: "StatifierUI", + source_ref: "v#{@version}", + canonical: "https://hexdocs.pm/statifier_ui", + source_url: @source_url, + main: "readme", + extras: + [ + "README.md", + "CHANGELOG.md", + "docs/architecture.md", + "docs/wire-format.md", + {"docs/adr/README.md", [title: "Architecture Decision Records", filename: "adr-index"]} + ] ++ Enum.sort(Path.wildcard("docs/adr/0*.md")), + groups_for_extras: [ + Guides: ~r{docs/(?!adr)}, + "Architecture Decision Records": ~r{docs/adr} + ] + ] + end + + defp package do + [ + name: "statifier_ui", + licenses: ["MIT"], + files: ~w(lib mix.exs README.md LICENSE CHANGELOG.md), + links: %{ + "GitHub" => @source_url, + "Changelog" => "#{@source_url}/blob/main/CHANGELOG.md" + } + ] + end + defp deps do [ - # Statifier is not published to hex yet, so the engine is a git dep and - # mix.lock pins the SHA. This becomes a version requirement the day - # statifier publishes; nothing else here depends on which form it takes. - {:statifier, github: "riddler/statifier-ex"}, + statifier_dep(), # Both integrations are optional: the package is a component library, and # a Livebook host has no reason to pull LiveView, or the reverse. Anything @@ -56,4 +93,14 @@ defmodule StatifierUI.MixProject do {:doctor, "~> 0.23", only: :dev, runtime: false} ] end + + # Export STATIFIER_PATH to point at a local checkout while co-developing a + # change that spans both repos. It is an env var rather than a mix.exs edit + # so the override never lands in a commit by accident. + defp statifier_dep do + case System.get_env("STATIFIER_PATH") do + nil -> {:statifier, "~> 2.0"} + path -> {:statifier, path: path, override: true} + end + end end diff --git a/mix.lock b/mix.lock index 61f2e50..5c0225f 100644 --- a/mix.lock +++ b/mix.lock @@ -26,7 +26,7 @@ "plug_crypto": {:hex, :plug_crypto, "2.2.0", "144014737daaf485407f5ed77daeaad74d651b216a28c87543f8cc7043f8efc8", [:mix], [], "hexpm", "83a95744ab1c75876542b6fab135fcc176280e0f301a111c1f757fddcec95d2c"}, "predicator": {:hex, :predicator, "9.0.0", "c15685ce74e249195df9bbaedf7593c50339ac75b8700a3b13f56cfbcc837ff7", [:mix], [], "hexpm", "8e31739e3d448f9f5d91eb716498a0dfe26f3b27c8bcf96bc6b2ce93c0087e95"}, "saxy": {:hex, :saxy, "1.6.1", "742eff28f553c066d0b54e84662dbf384a1d1f38595472ed15f6e0a33038bbe1", [:mix], [], "hexpm", "8989d504424ba29460a61950f8968380651413fa05e63b6118084db057da1a6b"}, - "statifier": {:git, "https://github.com/riddler/statifier-ex.git", "1d0c6ba18e48cfb6bec3f866702cf15993bbdff7", []}, + "statifier": {:hex, :statifier, "2.0.0", "826cd183113f83cb91f90f41b9e711b7df735219783d996e8ff4a48a40d96c93", [:mix], [{:predicator, "~> 9.0", [hex: :predicator, repo: "hexpm", optional: false]}, {:saxy, "~> 1.6", [hex: :saxy, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.3", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "8026d01db517058c01cf289f428e9a5c3c4c52ae092ddf698aefd5c4be263a50"}, "table": {:hex, :table, "0.1.2", "87ad1125f5b70c5dea0307aa633194083eb5182ec537efc94e96af08937e14a8", [:mix], [], "hexpm", "7e99bc7efef806315c7e65640724bf165c3061cdc5d854060f74468367065029"}, "telemetry": {:hex, :telemetry, "1.4.2", "a0cb522801dffb1c49fe6e30561badffc7b6d0e180db1300df759faa22062855", [:rebar3], [], "hexpm", "928f6495066506077862c0d1646609eed891a4326bee3126ba54b60af61febb1"}, "websock": {:hex, :websock, "0.5.3", "2f69a6ebe810328555b6fe5c831a851f485e303a7c8ce6c5f675abeb20ebdadc", [:mix], [], "hexpm", "6105453d7fac22c712ad66fab1d45abdf049868f253cf719b625151460b8b453"}, diff --git a/test/statifier_ui/trace/normalizer_test.exs b/test/statifier_ui/trace/normalizer_test.exs index 8e866c8..b6dadb2 100644 --- a/test/statifier_ui/trace/normalizer_test.exs +++ b/test/statifier_ui/trace/normalizer_test.exs @@ -370,7 +370,8 @@ defmodule StatifierUI.Trace.NormalizerTest do macrostep: 1, microstep: 0, id_from_author?: false, - round: 0 + round: 0, + ordinal: 1 } assert {:ok, %Message{type: "effect.send_delayed"} = message} = @@ -391,7 +392,8 @@ defmodule StatifierUI.Trace.NormalizerTest do owner: {:transition, 0}, macrostep: 1, microstep: 0, - round: 0 + round: 0, + ordinal: 1 } assert {:ok, %Message{type: "effect.cancel"} = message} = @@ -1012,7 +1014,8 @@ defmodule StatifierUI.Trace.NormalizerTest do macrostep: 1, microstep: 0, id_from_author?: true, - round: 0 + round: 0, + ordinal: 1 } end @@ -1023,7 +1026,8 @@ defmodule StatifierUI.Trace.NormalizerTest do owner: {:transition, 0}, macrostep: 1, microstep: 0, - round: 0 + round: 0, + ordinal: 1 } end