Development, quality, and release workflows for the dic_ex package. End users
only need the README; this file is for people working on the
package itself.
Development setup
mix deps.get # or: mix setup (alias)
mix compile
mix.lock is intentionally not committed — libraries don't ship lockfiles
(see .gitignore). Don't stage it in commits.
Quality gates
Run all four before opening a PR or cutting a release:
mix format --check-formatted
mix credo
mix test
mix dialyzer # type checks the @spec'd API (first run builds a PLT)
CI (.github/workflows/ci.yml) runs the same on every push and pull request:
formatting + credo on a single version, the test suite on an Elixir/OTP matrix,
and dialyzer.
Tests are deterministic by design: async: true everywhere, and randomness is
threaded as {DicEx.RNG.Deterministic, [outcomes]} or pinned with seed: — no
shared/global state.
Working on the frontend assets
The 3D dice (Three.js + Rapier) live in assets/src/ and ship as a prebuilt
priv/static/dic_ex.min.js (~2.7 MB) inside the Hex package. Rebuild it after
editing assets/src/:
mix dic_ex.build # alias: mix build → one-shot minified build
node assets/build.mjs --watch # or: pnpm --dir assets watch → fast rebuilds on change
Gotchas:
- The bundle is a committed artifact.
priv/static/dic_ex.min.jsis not gitignored and is listed inmix.exspackage: files:. Always rebuild and commit it; a stale bundle ships a stale UI. - The build only emits the JS.
priv/static/dic_ex.cssis hand-authored and is never touched by esbuild — edit it directly. assets/js/is vestigial and empty. Real sources areassets/src/; the esbuild entry point isassets/src/index.js(setswindow.DicExHooks).- ~2.7 MB is expected for
dic_ex.min.js: Rapier's WASM is inlined as base64 so consumers serve a single file. 2D-only apps usedic_ex_2d.min.js(entryassets/src/index_2d.js, a few KB). - Package manager fallback is pnpm → bun → npm (the build task auto-installs
on first run). The project standard is pnpm (
assets/pnpm-lock.yaml).
Releases
A release is: version bump → changelog → quality gates → tag → Hex publish. The
version lives in three places that must agree — mix.exs (@version, line 4),
CHANGELOG.md (latest dated heading), and the git tag (v<version>).
- Bump
mix.exs— the@versionmodule attribute only. - Update
CHANGELOG.md(Keep a Changelog): promote the current## [Unreleased]block into a dated section (## [x.y.z] - YYYY-MM-DD, ISO date) and add a fresh empty## [Unreleased]above it. Keep only subsection headers that have entries. - Verify consistency:
grep -m1 '@version' mix.exs grep -m1 '^## \[' CHANGELOG.md git tag --list 'v*' - Quality gates (see above) must pass.
- Rebuild assets if
assets/src/changed (see above), and commit the newpriv/static/dic_ex.min.js/dic_ex_2d.min.jsinto the release (CI fails if they're stale). - Commit and push. Commit style is lowercase imperative:
Check the file list withgit add mix.exs CHANGELOG.md git commit -m "release: vx.y.z" git pushmix hex.build(the tarball is gitignored). - Publish a GitHub release with tag
v<version>(the tag must bev<version>becausemix.exssetssource_ref: "v#{@version}"):gh release create vx.y.z --title "vx.y.z" --notes-file <(changelog section).github/workflows/publish.ymlthen checks that the tag,mix.exsandCHANGELOG.mdagree, runs the quality gates and runsmix hex.publish(package + docs). It needs theHEX_API_KEYrepository secret:
Thenmix hex.user key generate --key-name github-actions --permission api:writemix hex.info dic_exto confirm.
Release gotchas:
- A Hex version, once published, is immutable — Hex rejects re-publishing the
same
<package>-<version>(after the first hour). Verify the version and tarball contents withmix hex.buildbefore publishing the GitHub release. If the workflow fails before publishing, fix it and re-run the job; don't reuse a tag that already reached Hex. - Tag format is
v<version>, not bare0.2.0. A bare/missing tag breaks ex_doc source links. - Pre-1.0 SemVer: while at
0.x, breaking changes are allowed in a MINOR bump. After 1.0, follow strict SemVer. - Optional deps stay optional.
phoenix_live_view/phoenix_html/jasonareoptional: true; don't make them hard deps. The web component is conditionally compiled (Code.ensure_loaded?), so the core compiles with no web deps. MIX_ENVdoesn't matter for library publishing — there's nomix releasehere.
Conventions
- Namespaces:
DicEx(core) andDicExWeb(optional web layer); mix tasks underMix.Tasks.DicEx.*. One module per file. - Internal modules carry
@moduledoc false; public modules documentiex>examples (doctest-ready) and@specs. - RNG is a behaviour (
DicEx.RNG) threaded explicitly through evaluation for reproducibility — never call:randad hoc inside the pipeline. - Commits: lowercase imperative summary (
docs: ...,release: ...).