fix: #3591876 Validate the target region before placing components

Fixes #3591876 (closed).

Problem

  • The AI targets a region the layout does not have.
  • CanvasAiPageBuilderHelper throws Region "%s" not found in layout, and the tool returns that message to the agent.
  • The message names no valid regions, so the agent can only guess a correction.

Approach

The issue proposes falling back to the first available region. That keeps the operation alive. The trade-off is that components land where the user did not ask, and the agent is never told its region name was wrong. This MR keeps the failure visible and separates it by cause.

  • No usable layout. The page builder and template builder both need a layout with at least one region. The controller detects this before any agent runs, and the chat UI shows Unable to read the page layout. Please reload the page and try again.
  • Unknown region name. The tools validate the AI's region before any layout is mapped. The agent receives Region "sidebar" does not exist. Available regions are: header, content, footer. and rebuilds the component structure with a region that exists.

Changes

File Change
Controller/CanvasBuilder.php Refuses a request whose layout has no regions, before any agent runs.
CanvasAiPageBuilderHelper.php Adds validateRegionExists() as the single region check. Drops the Region "%s" not found in layout throw from placeComponentsInside().
Plugin/AiFunctionCall/PlaceComponents.php Validates target against the layout regions.
Plugin/AiFunctionCall/SetAIGeneratedComponentStructure.php Same check. Also requires target to be present and a string.
Plugin/AiFunctionCall/SetAIGeneratedTemplateData.php Uses the shared check in place of its own inline region test.
ui/…/AiWizard.tsx Sends current_layout on the request path carrying an attached image.
Kernel/CanvasAiBuilderControllerTest.php New. Covers the controller refusal.
…/PlaceComponentsTest.php Cases for an unknown region name.
…/SetAIGeneratedComponentStructureTest.php Cases for an unknown region name and an invalid target.

Testing

Region validation, via ai_api_explorer

  1. Install Drupal CMS2 with the starter template. The steps below use components from it.
  2. Install ai_api_explorer. Log in as user 1.
  3. Seed the layout the tools read. This one has a single region, content:
ddev drush php:eval "
  \Drupal::service('account_switcher')->switchTo(\Drupal\user\Entity\User::load(1));
  \Drupal::service('canvas_ai.tempstore')->setData(
    'current_layout',
    json_encode([
      'regions' => [
        'content' => [
          'nodePathPrefix' => [0],
          'components' => [],
        ],
      ],
    ])
  );
  echo 'done';
"
  1. Go to /admin/config/ai/explorers/tools_explorer.

set_component_structure

Select the tool, paste into component_structure and run. Replace the component with any one available on your site.

operations:
  - target: sidebar
    placement: inside
    components:
      - sdc.mercury.hero-billboard:
          props:
            height: full
            flex_position: center-left
            overlay_opacity: "0%"
            object_position: center
            media:
              src: /themes/contrib/mercury/components/hero-billboard/assets/luke-chesser-pJadQetzTkI-unsplash.jpg
              alt: "Green blue gradient placeholder image"
              width: 1920
              height: 1344
            overlap_navbar: false
  • Returns Region "sidebar" does not exist. Available regions are: content. Before this MR it returned Region "sidebar" not found in layout, which names no alternative.
  • With target: content, returns Component structure processed successfully.
  • With the target key removed, returns The target key is missing or invalid in the operation.

set_template_data

Select the tool. Its input is region-keyed YAML with no target key. Paste into component_structure and run.

sidebar:
      - sdc.mercury.hero-billboard:
          props:
            height: full
            flex_position: center-left
            overlay_opacity: "0%"
            object_position: center
            media:
              src: /themes/contrib/mercury/components/hero-billboard/assets/luke-chesser-pJadQetzTkI-unsplash.jpg
              alt: "Green blue gradient placeholder image"
              width: 1920
              height: 1344
            overlap_navbar: false
  • Returns Region "sidebar" does not exist. Available regions are: content.
  • With content as the top-level key, the run succeeds.

place_components

This tool cannot be exercised from the explorer. Its operations context is a list of records (ComplexToolItems), not a YAML string, so the form cannot build its input. It is also inert, not yet in any agent's tool set. Its region check is covered by PlaceComponentsTest.

Controller refusal, in the chat UI

  1. In modules/canvas_ai/src/Controller/CanvasBuilder.php, temporarily force an empty layout on the line above the new check:
$prompt['current_layout'] = [];
  1. Open a Canvas page in the editor and send any prompt.
  • The chat shows Unable to read the page layout. Please reload the page and try again.
  • No agent progress items appear, so no agent ran.
  1. Remove the line and send the same prompt.
  • The page builds as before.

Automated

ddev exec vendor/bin/phpunit -c ./web/core/phpunit.xml.dist \
  web/modules/contrib/canvas/modules/canvas_ai/tests/src/Kernel/CanvasAiBuilderControllerTest.php

ddev exec vendor/bin/phpunit -c ./web/core/phpunit.xml.dist \
  web/modules/contrib/canvas/modules/canvas_ai/tests/src/Kernel/Plugin/AiFunctionCall/

Generated with AI assistance.

Edited by AKHIL BABU

Merge request reports

Loading