{% load common_extras %} {% comment %} Step 3 — the message (SPEC §13.1, ROADMAP line 43). A plain HTMX/Alpine form that writes a single-node graph_json. No React embed: the canvas belongs to the flow builder and a broadcast is one message. What IS reused is the config panels' data shapes — apps/flows/schema/nodes.py's message_block, message_button and quick_reply fragments — so the same validator, the same sanitiser and the same engine node serve both. **Not one platform name appears below.** Every affordance is a question put to `composer`, which apps/broadcasts/composer.py assembles out of the capability table, the policy table, the template registry and the segment counter: composer.blocks which block kinds this platform renders composer.allows_buttons max_buttons > 0 composer.tag_choices policy.outside_window being a NeedsTag composer.tag_allowed_use_text Meta's own copy, displayed verbatim (SPEC §6.4) composer.templates approved_templates_for + variable_schema composer.cost_hint the workspace's own per-category estimates composer.counts_segments whether the SMS segment preview applies An `if` comparing a platform name here would be the first crack in contract 4's promise that a Layer-5 platform costs one module and one registry line, and test_composer.py scans these templates for exactly that comparison. Which is why the prose above does not spell one out — the scan is a plain substring search on purpose, the way templates/contacts/_filter_bar_script.html keeps a literal script tag out of its own comment for its extractor. {% endcomment %}