Setup for host applications. Component authoring is docs/AUTHORING.md; the
normative wire contract is docs/CONTRACT.md.
1. Dependency
# mix.exs
defp deps do
[
{:hologram, "~> 0.10"},
{:noizu_tessera, "~> 0.1"}
]
endnoizu_tessera keeps hologram as an optional runtime:false dependency, so
non-Hologram hosts (plain Elixir + npm consumers of the generated contracts)
depend on it without pulling the framework.
2. Install the JS side
Add the dependency yourself first (§1 above — mix tessera.install does NOT
edit mix.exs), then run:
mix tessera.install
- wires
@noizu/tessera-coreintoassets/package.json(file:link into the installed dependency'spriv/js, always a relocatable RELATIVE path — including when the dep lives outside the app tree — plus lit^3as a peer; existing keys are never overwritten; re-runs are idempotent; dependencies that ship their own tesserapriv/jsruntime getfile:entries too); - runs
npm installunless you pass--no-npm; npm missing from PATH fails with install guidance instead of a raw error.
Hosts that manage their own JS packaging can skip the npm leg and add
@noizu/tessera-core from their registry of choice themselves.
Boot hazard: export HOLOGRAM_START=1 for the whole boot chain
In a fresh Linux environment the canonical boot chain
mix deps.get && mix tessera.install && mix holo can hang indefinitely:
mix deps.get compiles path deps immediately, and if that compile runs
without HOLOGRAM_START=1, the later mix holo (which sets it) finds the
Hologram compiler already registered and loops forever on
Hologram: compiler already running, waiting... — hologram 0.10.1 never
times out. Observed twice in an elixir:1.18-otp-27 container, never on macOS.
Gate the WHOLE chain on the flag, before mix deps.get:
export HOLOGRAM_START=1
mix deps.get && mix tessera.install && mix holo
CI's e2e jobs export it for the whole chain as a hardening; the full
investigation lives in docs/notes/e2e-linux-ci.md (§ "Boot-recipe hazard").
3. Declare components
Author components per docs/AUTHORING.md. Compile — the contracts,
__tessera_spec__, and (in Hologram) the render/bridge glue are generated at
compile time; nothing to run by hand.
4. Aggregate manifests
defmodule MyApp.Tessera do
use Noizu.Tessera.Package, components: [MyApp.Components.PersonaCard, ...]
endmix tessera.manifest
writes tessera-manifest.json (full specs) and custom-elements.json
(Custom Elements Manifest, schemaVersion "1.0.0") into the current directory —
commit them where your tooling or downstream consumers expect.
mix tessera.cem writes just the CEM for the current project's package(s)
(--package Module to scope, --output PATH to relocate). The package module
also exposes it programmatically as cem/0.
Interactive gallery
mix tessera.gen.gallery scaffolds a framework-agnostic interactive gallery
from the package CEM — live demos, typed attr/prop editors, slot-content
editing, CSS custom-property theming and an event log per component. See
docs/AUTHORING.md § Gallery for the build/serve steps;
examples/persona_kit/gallery is a working, e2e-tested instance.
5. Runtime model (what the wiring does)
- Server render: tessera components render as their tag with kebab-case
attributes; boolean attrs render by presence (
false/nildrop the attribute); json props ship astsr-props(camelCase keys, HTML-encoded in the served HTML, decoded by the DOM); bound events render asdata-tsr-on="event:action@target". - Registration: the top-level
register()call at the end of each component's.mjsis load-bearing — stateless SSR never callsinit/3, so the element gets defined only when the page bundle evaluates the.mjsand reaches that statement. The.mjsitself is bundled because the generatedinit/2/init/3pair calls itsregisterexport through an absolute-pathjs_importbinding, keeping it on the page call graph.mix compilewarns if a component's.mjsis missing the top-level call. Stateful instances additionally register through those inits.defineOncein@noizu/tessera-coremakes duplicate registration (multiple bundles) warn-once instead of throwing. - Events: element → Elixir actions flow through the bridge described in
CONTRACT.md §4. Bind an action atom (
on_persona_select={:chose}) or a tuple with an explicit target ({:chose, target: "layout"}or{:chose, target: cid}— CONTRACT.md §3 supports cid targets only through the tuple form; bare binaries are rejected with an ArgumentError). With NO binding (nil/false, or none passed) NOdata-tsr-onentry is rendered for that event and NOTHING dispatches when the element emits it — the event is then just a plain composed DOMCustomEventthat only plainaddEventListenerconsumers see. There is no implicit default action.
6. Versioning note
The Elixir package and @noizu/tessera-core version in lockstep (0.1.x) —
the wire contract (v1) is what they share, so minor mismatches are tolerable,
but keep them in the same major.