The section/ module models everything the editor and renderer agree on:
node schemas (from {% schema %}), page configurations (the persisted
JSON), and the recursive node renderer. Sections and blocks are the same
thing now, a node, with a role hint for the editor.
NodeSchema
Parsed from each node file’s {% schema %} (fields documented in
Schema blocks):
pub struct NodeSchema {
pub role: NodeRole, // Section | Simple (presentation hint only)
pub name: String,
pub tag: HtmlTag, // section | div | header | footer | article | aside
pub class: Option<String>,
pub limit: Option<u32>,
pub settings: Vec<SettingDefinition>,
pub accepts: Vec<ChildSpec>, // Named(type) | AnyTheme("@theme") | AnyApp("@app")
pub max_children: u32, // default 50
pub slots: Option<HashMap<String, SlotDefinition>>,
pub presets: Vec<NodePreset>,
pub default: Option<NodePreset>,
pub locales: HashMap<…>,
pub enabled_on: Option<Vec<String>>,
pub disabled_on: Option<Vec<String>>,
}ChildSpec deserializes from JSON strings: "slide" → Named, "@theme"
→ AnyTheme, "@app" → AnyApp. @theme is expanded at load time to
every registered theme node not already listed (section/registry.rs);
@app is a stub, the marker is removed and no app nodes are injected.
SlotDefinition { accepts, max }, the per-slot child rules. Nodes without
slots get one implicit default slot using accepts + max_children.
Page config: the map + order pattern
Persisted page/group configs (store_templates.config) use a map + order
pattern at every depth:
pub struct PageConfig {
pub sections: HashMap<String, NodeInstance>, // identity in the map key
pub order: Vec<String>, // display order
pub wrapper: Option<String>, // preview/editor targeting
pub layout: Option<String>, // optional layout override
}
pub struct NodeInstance {
pub r#type: String,
pub disabled: bool,
pub settings: HashMap<String, serde_json::Value>,
pub children: HashMap<String, NodeInstance>, // old "blocks" key accepted
pub child_order: Vec<String>, // old "block_order" accepted
pub slot: String, // "default" unless named slot
}The stable IDs in the map keys are what make drag-to-reorder, fragment
hot-swap, and editor targeting work. TemplateConfig is an untagged enum
over PageConfig (any page key) and ThemeSettingsConfig (the special
_settings key: color_scheme, heading_font, body_font, plus a
flattened catch-all extra map).
Resolution: config → resolved tree
resolve_node_config(page, theme) walks page.order[], looks up each
instance’s schema in the theme, applies setting defaults, and resolves
children recursively, skipping disabled nodes and warning (not failing)
when a type no longer exists in the theme:
pub struct ResolvedNode {
pub id: String,
pub instance: NodeInstance, // settings defaults merged in
pub schema: NodeSchema,
pub resolved_children: Vec<ResolvedNode>,
}Rendering: the recursive node renderer
render_node(engine, slug, node, global, depth):
- Recursively renders all children, collecting each child’s rendered HTML
into
slots_html(keyed by the child’sslot) and intoall_children(asNodeObjects, grandchildren included sorender_node(child)can re-render nested templates inline). - Builds the
NodeObjectfor this node,children_renderedis thedefaultslot’s HTML. - Looks up
{slug}/nodes/{type}.jinjaand renders it withbuild_node_context: the storefront globals (optional ones stubbed) plusnode. - Wraps the result via
wrap_nodeand returns(html, rendered_type_names), the type set drives CSS subsetting.
wrap_node
<!-- depth 0 (top-level) -->
<section id="rr-node-{id}" class="rr-node rr-node--hero rr-hero-wrapper">…</section>
<!-- depth > 0 adds editor hooks -->
<div id="rr-node-{id}" data-rr-node="{id}" data-rr-type="text-block" class="rr-node rr-node--text-block">…</div>The class always includes rr-node rr-node--{type-slug} plus the schema’s
class. node.attributes in templates emits the data-rr-node /
data-rr-type pair so children rendered inside a parent’s markup (e.g.
in a loop) stay editor-targetable.
The NodeObject template surface
Templates see node as a NodeObject (section/objects.rs): id,
type, settings (a NodeSettingsObject over the JSON map), children
(recursive NodeObject array), children_rendered, slots (per-slot
HTML), slot, tag, class, attributes. JSON values convert through
serde_to_minijinja, objects become traversable attribute-access
wrappers, so node.settings.colors.primary works.
Section groups
section/groups.rs resolves and renders the header/footer groups:
render_section_group(engine, slug, group_key, global) looks up the
group’s preset from the manifest, resolves it, and renders every node,
the same code path pages use. The API binary calls this for
@root:header-group and @root:footer-group when the page template wants
header_html / footer_html.