Packages
statifier_blocks
0.42.1
0.42.1
0.42.0
0.41.0
0.40.0
0.39.0
0.38.0
0.37.0
0.36.1
0.36.0
0.35.1
0.35.0
0.34.0
0.33.0
0.32.0
0.31.0
0.30.0
0.29.0
0.28.0
0.27.0
0.26.0
0.25.0
0.24.0
0.23.0
0.22.0
0.21.0
0.20.0
0.19.0
0.18.0
0.17.0
0.16.0
0.15.0
0.14.0
0.13.0
0.12.0
0.11.0
0.10.0
0.9.0
0.8.0
0.7.0
0.6.0
0.5.0
0.4.0
0.3.0
0.2.0
0.1.0
Block document model, one-way SCXML compiler, and LiveView editor components for composing Statifier statecharts
Current section
Files
Jump to
Current section
Files
statifier_blocks
mix.exs
mix.exs
defmodule StatifierBlocks.MixProject do
use Mix.Project
@version "0.42.1"
@source_url "https://github.com/riddler/statifier_blocks"
# ADR-0005 decision 1's acceptance property: the package must compile clean
# and its non-LiveView suite must pass with `phoenix_live_view` absent from
# the dependency tree. `STATIFIER_BLOCKS_HEADLESS=1` is how that tree is
# produced. It drops the optional dependency *and* redirects the deps,
# build and lock paths to headless-only siblings, so resolving a
# Phoenix-free tree can never disturb the ordinary one - in particular it
# can never rewrite `mix.lock`, which a plain `mix deps.get` with the
# dependency removed absolutely would.
@headless System.get_env("STATIFIER_BLOCKS_HEADLESS") in ["1", "true"]
def project do
[
app: :statifier_blocks,
version: @version,
elixir: "~> 1.18",
elixirc_paths: elixirc_paths(Mix.env()),
deps_path: if(@headless, do: "deps_headless", else: "deps"),
build_path: if(@headless, do: "_build_headless", else: "_build"),
lockfile: if(@headless, do: "mix.headless.lock", else: "mix.lock"),
start_permanent: Mix.env() == :prod,
deps: deps(),
name: "StatifierBlocks",
description:
"Block document model, one-way SCXML compiler, and LiveView editor components for composing Statifier statecharts",
source_url: @source_url,
docs: docs(),
package: package(),
test_coverage: [tool: ExCoveralls],
# `:mix` joins the PLT for the gate tooling under `lib/mix/`, which is a mix
# task and a plain module beside it; statifier-ex's own gate does the same.
dialyzer: [plt_add_apps: [:mix, :ex_unit]],
preferred_cli_env: [
coveralls: :test,
"coveralls.detail": :test,
"coveralls.html": :test
]
]
end
def application do
[
extra_applications: [:logger]
]
end
defp elixirc_paths(:test), do: ["lib", "test/support"]
defp elixirc_paths(_), 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: "StatifierBlocks",
source_ref: "v#{@version}",
canonical: "https://hexdocs.pm/statifier_blocks",
source_url: @source_url,
main: "readme",
extras: [
"README.md",
"docs/describing-a-document.md",
"CHANGELOG.md"
],
# The extras are grouped by the kind of page, in the family's order:
# Tutorials, How-to guides, Reference, Explanation, Upgrading. A group
# appears only when it has a page. The README (the main page) and the
# CHANGELOG stay ungrouped at the top. Decision records and planning
# notes are for contributors and are not extras.
groups_for_extras: [
"How-to guides": ["docs/describing-a-document.md"]
],
# Without this the first hexdocs is one flat list of ~50 modules, which
# tells a reader nothing about which of them they are meant to call. The
# groups follow the package's own seams - the document is the source of
# truth, block types are the extension seam, the compiler is one way, and
# the editor is the optional LiveView half - so the sidebar reads as the
# architecture rather than as the alphabet. Order matters: ex_doc assigns
# each module to the first group whose pattern matches.
groups_for_modules: [
"Document model": [
~r/^StatifierBlocks\.(Document|Block|Id|CanonicalJson|Decode|Validation|SlotValidation|Schema)($|\.)/
],
"Block types and assignability": [
~r/^StatifierBlocks\.(BlockType|Palette|Assignability)($|\.)/
],
"Core block vocabulary": [
~r/^StatifierBlocks\.Core($|\.)/
],
Compiler: [
~r/^StatifierBlocks\.(Compiler|Compiled|CompilationRecord|Emission|Provenance)($|\.)/
],
"Describing a document": [
~r/^StatifierBlocks\.Describe($|\.)/
],
"Edit algebra and view model": [
~r/^StatifierBlocks\.(Edit|Finding|ViewModel)($|\.)/
],
"The Map": [
~r/^StatifierBlocks\.Map($|\.)/,
~r/^StatifierBlocks\.Editor\.MapRegions$/
],
"LiveView editor": [
~r/^StatifierBlocks\.Editor($|\.)/
],
"Predicate evaluation": [
~r/^StatifierBlocks\.Predicates($|\.)/
]
],
skip_undefined_reference_warnings_on: ["CHANGELOG.md"]
]
end
defp package do
[
name: "statifier_blocks",
licenses: ["MIT", "EPL-2.0"],
# `assets` is in the list because the hook and the stylesheet ship as
# source (ADR-0005 decisions 7 and 14, sui-ADR-0009's delivery model),
# and source that is not in the tarball is not public API however
# carefully it is versioned. The record calls this out by name.
# `lib/statifier_blocks` rather than `lib`, because `lib/mix` holds this
# repo's own gate tooling. A mix task in the tarball would install itself
# into every project that depends on this package, which is not something
# a dependency should do to its host.
# `priv/schemas` because the block-document JSON Schema ships as a public
# file a host validates against, and it is named narrowly rather than as
# `priv` so a later addition under priv/ has to say that it ships.
files:
~w(lib/statifier_blocks lib/statifier_blocks.ex priv/schemas assets mix.exs README.md LICENSE CHANGELOG.md),
links: %{
"GitHub" => @source_url,
"Changelog" => "#{@source_url}/blob/main/CHANGELOG.md"
}
]
end
defp deps do
[statifier_dep()] ++
live_view_dep() ++
[
# The datamodel document, its path/type index and the declared types
# live here now: `sd-ADR-0001` carries ADR-0006's record forward into
# a package that depends on nothing else in the family, and
# `StatifierBlocks.Datamodel` reads that package rather than a second
# copy of the same projection.
#
# The floor is 0.3 and not 0.1. Every module this package names -
# `StatifierDatamodel.Index`, `StatifierDatamodel.Document`,
# `StatifierDatamodel.Types` and `StatifierDatamodel.Declarations` -
# shipped in 0.1.0, that package's first release, but 0.3.0 is where
# an entry's `type`, and a `list` entry's `item_type`, may name a
# declaration the document's `types` key declares (`sd-ADR-0001`'s
# amendment of 2026-09-06): the entry carries `{:declared, name}` and
# the declaration's fields are indexed beneath its own path, exactly
# as an inlined `object` entry contributes its `fields`.
# `StatifierBlocks.Datamodel` reads both through that index and
# `StatifierBlocks.Shell.declared_shape/1` renders the new
# inhabitant, so 0.1 or 0.2 would resolve here and then answer a
# narrower set of declared paths than this package now says it reads.
# The floor is 0.4 and not 0.3. `sd-ADR-0001`'s inline-shape amendment
# of 2026-09-06 shipped in 0.4.0, and its `{:shape, members}` term is
# what `StatifierBlocks.Environment`'s type expression now admits and
# what the read check hands `StatifierDatamodel.Types.satisfies/3` when
# a block carries an inline shape. 0.3 resolves and then has no such
# inhabitant at all. Raising it also widens what a document projects:
# 0.4 keeps 0.3's declared-name arm, so a declaration-typed entry still
# flattens into that declaration's fields.
# Below 1.0 a `~>` requirement admits only the 0.4.x line, so a
# breaking 0.5 does not resolve here without a deliberate bump.
{:statifier_datamodel, "~> 0.4"},
# Direct because `StatifierBlocks.Core.Duration` calls
# `Predicator.Duration.parse/1` to read a stored predicator duration
# string. It already resolves through statifier, so naming it here
# records the call rather than moving the lock.
#
# The floor is 9.4.1, and the reason is this package's own capture
# literals rather than a module it names. Every predicator module
# named here - `Predicator.Duration`, `Predicator.evaluate/2`,
# `Predicator.Errors.*`, `Predicator.Lexer` - shipped in 9.0.0, and
# by modules alone 9.0 would still be enough: the 9.2 module is
# `Predicator.Simple`, nothing here calls it, statifier-ui does and
# requires `~> 9.2` itself, and statifier-ui is *optional* here.
#
# What raises the floor is a behaviour. `StatifierBlocks.Core.OnEvent`
# emits a `capture` literal as a predicator string literal and the
# engine reads it back into the datamodel, so this package needs a
# lexer that returns the whole of a string. 9.4.0's did not: it wrote
# a literal's codepoints back a byte at a time, which mangled every
# character above ASCII. 9.4.1 fixed that lexer.
#
# Decision, 2026-09-13 (ADR-0002, the Note of this date): the interim
# of 2026-09-12 ended here. `OnEvent` refused a non-ASCII capture
# literal for exactly as long as the resolved predicator truncated
# one; the refusal is lifted and the floor rises with it, in one
# request, because round-tripping the whole of a string is now
# something this package NEEDS and a need is what a floor is allowed
# to state. `~> 9.4.1` admits 9.4.1 and later 9.x and excludes the
# 9.4.0 that truncates. What says the version is there is a positive
# round trip over a non-ASCII literal, in
# `test/statifier_blocks/core/on_event_test.exs`: it goes red against
# a resolved predicator that truncates, which is the whole of what
# this requirement is for.
{:predicator, "~> 9.4.1"},
# Dev / test
{:ex_quality, "~> 0.15", only: :dev, runtime: false},
{:credo, "~> 1.7", only: [:dev, :test], runtime: false},
{:dialyxir, "~> 1.4", only: [:dev, :test], runtime: false},
{:excoveralls, "~> 0.18", only: :test},
# A test-only validator for the shipped block-document schema; never a
# runtime dependency, since the package ships no validator of its own.
{:ex_json_schema, "~> 0.11", only: :test, runtime: false},
{:ex_doc, "~> 0.40", only: :dev, runtime: false}
]
end
# ADR-0005 decision 1. The requirement and the flag are copied deliberately
# from statifier-ui's `mix.exs`: two packages in one family disagreeing
# about the LiveView floor is a problem a host discovers at dependency
# resolution, and there is no reason to create it. No `phoenix_html`,
# `esbuild` or `tailwind` - the first arrives transitively, the other two
# belong to the host (sui-ADR-0009).
#
# The empty list is the headless tree, and it is the only place in this
# file that decides whether the guard around `StatifierBlocks.Editor.*`
# has anything to guard.
# `lazy_html` rides along because `Phoenix.LiveViewTest` refuses to run
# without it. It is `only: :test` and never reaches a host, and it belongs in
# this list rather than beside the other test dependencies precisely because
# it is useless in the headless tree - nothing there drives a LiveView.
defp live_view_dep do
if @headless do
[]
else
[
{:phoenix_live_view, "~> 1.0", optional: true},
# sb-m6e0: an `:expression` field renders statifier-ui's expression
# editor when this resolves, and the plain source input when it does
# not, so the dependency is optional in the same sense LiveView is
# and `StatifierBlocks.Editor.Field` guards on it the same way.
#
# It belongs in this list rather than beside the unconditional
# dependencies because its only consumer is a LiveView component:
# the headless tree has no editor to render, so pulling it there
# would resolve a package nothing in that tree can call.
{:statifier_ui, "~> 0.9", optional: true},
{:lazy_html, ">= 0.1.0", only: :test}
]
end
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.2"}
path -> {:statifier, path: path, override: true}
end
end
end