Issue #3586361: Decouple context item form descriptions from scope plugin descriptions.

Description

Adds getItemFormDescription() to AiContextScopeInterface so a scope plugin can show different guidance on a context item form than on the scope overview and settings pages. It defaults to getDescription(), so every existing plugin keeps its current behaviour without an override.

AiContextScopeGlobal already needed this and worked around it privately: it carried a protected getFormDescription() whose docblock read "Uses a line break that is not safe for settings pages escaped with Html::escape(), so keep plain text in getDescription() instead." That was invisible to the API, so no other scope could do the same thing. This promotes it to a documented interface method.

Interface (src/Plugin/AiContextScope/AiContextScopeInterface.php)

  • Added getItemFormDescription().
  • Rewrote the getDescription() docblock. It previously said "Gets the scope description for context items" — the source of the confusion, since the overview and settings pages use it too. It now describes itself as the general description and records that both those pages escape it with Html::escape(), so it must be plain text.
  • Updated the $for_subscription parameter docs on buildValueForm() and added @see cross-references between the three description methods.

Base classgetItemFormDescription() returns getDescription(); buildValueForm() selects it for the item form.

AiContextScopeSiteSection, AiContextScopeTag — same one-line change; both override buildValueForm() and duplicated the ternary.

AiContextScopeTargetEntity — its #description sits in the branch reached only for item forms, so it now uses the new method.

AiContextScopeGlobalprotected getFormDescription() becomes public getItemFormDescription(), warning and <br> unchanged; and "Saving the form with this enabled will clear previous scope settings." is removed from the plugin attribute description, so the overview and settings pages no longer mention saving a context item.

AiContextScopeLanguage needed no change — its single-language branch renders an info item with no #description, and its multi-language branch delegates to parent::buildValueForm().

The result

The same plugin, two different descriptions, chosen by which form is rendering:

Scope overview — no save/clear warning

image

Context item form — warning kept, on its own line

image

Documentationdocs/developers/scope_api.md lists all three description methods and gains a Choosing a description method section with a where-it-appears / which-method table, plus a note that getDescription() must be plain text while getItemFormDescription() may contain simple markup. docs/developers/custom_scopes.md no longer describes the attribute description as context-item-specific and links to that section.

Tests

tests/src/Unit/AiContextScopeItemFormDescriptionTest.php (new, 4 tests) covers the default fallback using AiContextScopeUseCase, following the direct-instantiation pattern already used by AiContextScopeAlteredValuesTest: the method equals getDescription(); the fallback also holds for the label-derived description when the plugin definition has no description key; buildValueForm(..., FALSE) prints it; and buildValueForm(..., TRUE) still prints getSubscriptionDescription().

tests/src/Unit/AiContextScopeGlobalTest.php gains 3 tests for the override: the item form description keeps the warning and the <br>, the general description does not, and the checkbox prints the item form description. The diff on that file is insertions only.

Unit suite goes from 156 to 163 tests.

Backwards compatibility

Adding a method to AiContextScopeInterface is technically a BC break for a third-party plugin implementing the interface directly instead of extending AiContextScopeBase. Nothing in the module does that — the abstract base is the only direct implementer and supplies a default — so no in-tree plugin needs an override. Noting it because the interface is public API.

Overlap with #3586359 / !234

!234 was open while this was written and touches two of the same files. It already removes the same sentence from the Global plugin attribute (an acceptance criterion here) and adds a testBuildValueFormDescription() method to AiContextScopeGlobalTest. This MR branches from 1.0.x; whichever lands first, the other rebases — the overlap is two small hunks and one test method.

Happy to retarget at 3586359-beta4-polish if you would rather they were stacked, and happy to move this method's documentation into the @defgroup ai_context_scope_api block in ai_context.api.php that !234 introduces, rather than docs/developers/scope_api.md, once it lands.

Testing instructions

Set up per the template, then check out this MR branch and ddev drush cr.

The cache rebuild is required, not optional — the change includes the description: value inside the #[AiContextScope] attribute, and plugin definitions are cached, so step 1 shows the old text without it.

  1. Go to /admin/config/ai/context/settings/scope (Settings → Scope). The Global row's description reads "Always include this context for every agent." and does not mention saving or clearing scope settings. On 1.0.x it currently also says "Saving the form with this enabled will clear previous scope settings.", which is meaningless on an overview page.
  2. Go to /admin/config/ai/context/settings/scope/global. Same text, same absence of the warning.
  3. Go to /admin/config/ai/context/items/add. Expand Context Scope. The Global context checkbox description shows both lines: "Always include this context for every agent." then, after a line break, "Saving the form with this enabled will clear previous scope settings."
  4. On that same form, expand Use Cases, Tags and Site Sections. They read "The use cases / tags / site sections relevant for this context." — unchanged from before this MR. This is the regression check: those plugins do not override the new method, so they fall through to getDescription().
  5. Go to /admin/config/ai/context/settings/agents, edit an agent, and expand Scope Subscriptions. Descriptions still use the subscription wording ("Prioritize context items for these use cases."), and Global is absent — the form's own note confirms "These Scope Plugins Do Not Allow Subscriptions: Global, Target Entities".

Verified locally on Drupal 11.4.4 (standard profile) — all five steps performed in a browser.

Automated verification, all exit 0:

  • Unit 163 tests / 365 assertions (baseline 156 / 353 on a clean checkout of the same commit before any edit)
  • Kernel 519 tests / 8,425 assertions
  • Functional 156 tests / 2,073 assertions
  • phpcs clean; PHPStan clean against the module's phpstan.neon.dist
  • cspell reports no new words

Two notes for anyone reproducing this locally:

  • FunctionalJavascript was not run — it needs a Selenium/chromedriver service DDEV does not provide and MINK_DRIVER_ARGS_WEBDRIVER is empty in web/core/phpunit.xml. This change touches no JavaScript.
  • The suite needs the module's whole require-dev installed. Without drupal/diff and drupal/ai_agents_debugger, 24 tests fail across AiContextScopeMapFieldDiffBuilderTest, AiContextItemScopeDiffTest, AiContextItemRevisionUiWithDiffTest, AiContextSchedulerIntegrationTest and AiContextAgentSettingsTest — none of them related to this change.

Checklist

  • I have updated the MR title to use format: Issue #1234: My issue title.
  • I have updated the MR description to include: Closes #1234
  • I have performed a self-review of my own code
  • I have added or updated tests, or explained in the description why this change is not covered by tests
  • I have updated documentation for any new or changed functionality
  • I have written testing instructions and verified them locally
  • I have noted any required post-merge steps (config imports, cache rebuilds, manual changes)
  • This MR contains no breaking API or hook changes, or they are explicitly documented in the description

Post-merge step: a cache rebuild is needed, because the #[AiContextScope] attribute's description for the Global scope changed and plugin definitions are cached. No config import or manual change is required.

AI Compliance

Note

Check the one that best describes your usage, or leave all unchecked if AI was not significantly used.

  • AI Assisted Code
    Mainly written by a human; AI used for autocomplete or partial generation under full human supervision.

  • AI Generated Code
    Mainly generated by AI, reviewed and approved by a human before this MR was created.

  • Vibe Coded
    Generated by AI and only functionally reviewed before this MR was created.

Claude Code was used to generate the code changes, both test classes, the documentation edits and this description. Every change was reviewed and applied by hand, the full test suite and static analysis were run locally, and all five testing steps above were performed in a browser before this MR was created.

Closes #3586361

Merge request reports

Loading