Packs

A pack is the vocabulary: which node types exist and how each one renders. Where a frame decides what document an image becomes — exclusive, one per render — a pack is additive. Hand a runtime two packs and you get the union of their types, with a card from one sitting beside a sparkline from another.

createIsomerRuntime({ packs: [componentsPack, chartsPack] });
		

Packs are built by definePrimitivePack in the SDK, not here — see the pack contract. This page is about what a runtime does with them.

Contribution Used for
primitives The definitions the dispatcher, validator, parser, and schema are built from
types Duplicate detection across packs
surfaces Which optional surfaces every primitive implements (only slack)
enhancements Progressive enhancements the HTML surface may apply, by id
slackAssetTypes Which node types are pictures, so Slack uploads them instead of approximating them
styleAdapter Optional. Its HTML CSS, combined with every other pack's
styleCollector Optional. The collector shape its collectStyles hooks expect
theme The palette its svg renderers require, via themeBound<T>()

The last facts are declared by the pack because they are facts about a vocabulary, and a host composing packs it did not write cannot be expected to know them. The runtime unions slackAssetTypes and enhancements across packs.

Two rules, both enforced when the runtime is built:

  • No node type may be registered by two packs. The dispatcher is keyed on type and nothing else, so a duplicate makes dispatch ambiguous.
  • No enhancement id may be registered by two packs, for the same reason one level up — the HTML surface flattens enhancements runtime-wide.

Everything else composes silently, with one bound to respect: a pack declares the palette its svg renderers read, and every pack in a runtime — plus every frame in its map — has to agree on it. A pack that declares svg and one that does not can still sit together; nodes from the second simply produce a validation warning if you try to draw them. Packs whose renderers want different palettes belong in different runtimes, which is what Frame covers.

Vocabulary and document are independent. Splitting them makes a pack purely additive. Nothing about a primitive decides the frame, so any pack's nodes can be drawn inside any frame.

What replaced frame ownership is a type-level bound rather than a runtime check — a pack states the palette it needs, and the compiler pairs it with a frame that supplies one.

A pack ships its own styleAdapter, and the runtime combines them: every adapter gets a collector, each style handle is routed to the adapter that owns it, and the emitted <style> is their concatenation. So styled packs are as additive as their vocabularies, and a host loading two of them supplies nothing.

The same routing applies to collectStyles: an adapter's walk fires the hooks of the pack that declared it and no other, so one pack's rules never land in another's stylesheet. A type no adapter-declaring pack owns still reaches every adapter, since nothing says where it belongs.

Routing needs ownsHandle on each adapter. Combining two where one cannot answer throws, naming the pack that should declare it. A pack is also CSS-bearing when any of its primitives declare collectStyles; construction throws if such a pack has no adapter at all, naming both the pack and the primitive.

A CSS-bearing pack should also declare styleCollector, naming the collector shape its hooks mutate — DISTILLATE_STYLE_COLLECTOR for a Distillate-backed pack. Adapters publish the same tag, and construction throws when they disagree. Without it the mismatch surfaces as a corrupted collector at render instead.

A host that wants a CSS-bearing pack on text or Slack only supplies a no-op adapter, which replaces every pack's; see Surfaces.

Frame · Renderer overrides · The pack contract