Family plugins
Each family has a schema under program/ and a definition under families/.
defineFamily() preserves the relationship between its program type and its
finite relation-style keys.
flowchart LR Schema["strict schema<br/>program/architecture.ts"] --> Define["defineFamily()<br/>families/architecture.ts"] Define --> Normalize["normalize()<br/>GraphIR"] Define --> Placement["placement()<br/>PlacementSpec"] Define --> Notation["notation()<br/>caps · dash · legend"] Normalize --> Prepared["PreparedFamily"] Placement --> Prepared Notation --> Prepared Prepared --> Shared["shared engine pipeline"]
Allowed responsibilities
Section titled “Allowed responsibilities”A family may:
- define and validate semantic input;
- normalize that input into shared nodes, relations, and groups;
- choose a configured placement strategy or supply a bespoke placer;
- map its finite relation styles to shared notation.
A family may not import routing, label, icon, colour, harness, or renderer code. Those restrictions are executable ast-grep and contract-test rules.
Registry lifecycle
Section titled “Registry lifecycle”FamilyRegistry is an instance class because it owns a configured set of
families for a meaningful lifetime. with(family) returns a new registry, so a
custom extension cannot mutate global state or affect another engine consumer.
flowchart LR Builtins["builtInFamilyRegistry"] --> LayoutA["layout call A"] Builtins --> With["registry.with(custom)"] --> Custom["isolated registry"] --> LayoutB["layout call B"] Custom -. "does not mutate" .-> Builtins
See Add a family for the implementation sequence.