0009 Front-end contracts¶
Status¶
Accepted
Context¶
The Answer and Question blocks, their Twig templates, CSS, and JS all have to stay consistent with each other and with the API wire protocol, without a compiler enforcing that consistency. This ADR records the contracts that must be preserved by hand.
Decision¶
- Cacheable static shells. Both blocks render only configuration and
drupalSettingsinto the render array. Answer content never gets baked into cached markup — it always arrives afterward via the API. - Load-bearing markup attributes. The Twig templates'
data-ai-answers-roleandhiddenattributes are read by JS and styled by CSS. Preserve them exactly when theming these templates. :not([hidden])CSS scoping. CSS for any element the template markshiddenmust scope its display rule with:not([hidden]), or the element is visible before JS has run. Getting this wrong shipped as bug 3610864 on the follow-up form and feedback container.- Single JS dispatch path.
Drupal.aiAnswers.answer.ask()is the only function that actually issues the request; there is no parallel implementation of the fetch itself. Chip click and same-page form submit go throughsubmit()/dispatchAsk()first; the cross-page fragment handoff and the same-block follow-up form callask()directly, since both already run on the Answer block's own page. Preserveask()as the single point of truth even when adding a new trigger. - Paired DOM id formula. The Answer block's rendered DOM id is
Html::getUniqueId('ai-answers-answer-' . instance_uuid)(AnswerBlock::build()).Html::getUniqueId()replaced a plainHtml::cleanCssIdentifier()call in 3613862 so a duplicated-but-not-yet-resaved block placement (e.g. via Layout Builder's "duplicate block") gets a deduplicated id instead of colliding with the original.QuestionBlock::getAnswerBlockOptions()still builds its target dropdown with the plainHtml::cleanCssIdentifier('ai-answers-answer-' . $uuid)formula, deliberately: it only needs a stable key per distinctinstance_uuidto list options, not the request-scoped dedup registrygetUniqueId()adds, and every legitimately-saved placement has a uniqueinstance_uuid, so the two formulas agree in every case except the narrow duplicated-and-unsaved one this issue already covers. Keep this asymmetry in mind before "fixing" the two call sites into looking identical again.
Consequences¶
Any change to block markup, CSS visibility rules, or the JS dispatch path must be checked against all five contracts above, not just the one file being edited.