Overview
Postponed on #3512385: Changes to code components are not visible in global regions until published.
If page title block is added to content, it results in an unrecoverable error. We should handle this in a way that doesn't result in an error. This could be either disallowing adding the block there or fixing the block.
Proposed resolution
Make ApiLayoutController leap ahead of #3491701: [later phase] ApiLayoutController must use the previewed route's controller, and override canonical content entity routes' received entity object slightly and make it assume that it's always used on a content entity route (which is indeed true in XB's UI today):
- use
$label_field_name = $entity->getEntityType()->getkey('label')to get the name of the label field - use
$body['entity_form_fields'][$label_field_name . '[0][value]']to get the current entity title - wrap the
->toRenderable()call in fiber execution similar to\Drupal\experience_builder\Plugin\DisplayVariant\PageTemplateDisplayVariant::build()
(Proposed in #4, approved by @lauriii in #5.)
User interface changes
Page title block works in XB UI's preview.
| Comment | File | Size | Author |
|---|---|---|---|
| #53 | Screen Recording - 1080p.mov | 36.72 MB | f.mazeikis |
| #49 | working.gif | 79.95 KB | wim leers |
| #49 | almost.gif | 97.11 KB | wim leers |
| #48 | Screenshot 2025-05-23 at 1.18.42 PM.png | 178.16 KB | wim leers |
| #46 | Screenshot 2025-05-23 at 12.29.19 PM.png | 487.31 KB | wim leers |
Issue fork experience_builder-3502371
Show commands
Start within a Git clone of the project using the version control instructions.
Or, if you do not have SSH keys set up on git.drupalcode.org:
- 3502371-make-page-title
changes, plain diff MR !764
- 3502371-alt-approach
changes, plain diff MR !1045
Comments
Comment #2
kristen polJust ran into this. Error in log:
Comment #3
kristen polComment #4
wim leersRelated: the generalized variant of this problem/symptom: #3485878: Server-rendered component instances should NEVER result in a user-facing error, should fall back to a meaningful error instead (+ log).
Fixing: not possible until much later, and why
This is not possible until maybe #3491701: [later phase] ApiLayoutController must use the previewed route's controller, and override canonical content entity routes' received entity object, because this is one of the two highly special blocks in Drupal: they're fed information resulting from the execution of the route's controller!
Hence the error message, which originates from:
Note that the "main content" block has the same problem, and triggers the same exact error message (I just manually tested to verify). Issue title updated. 👍
And even then (after #3491701) this would be tricky to get right: if you're modifying the title of a node, then we'd have to:
_titlecallback executed:\Drupal\Core\Entity\Controller\EntityController::title()\Drupal\experience_builder\Plugin\DisplayVariant\PageTemplateDisplayVariant\Drupal\Core\Block\TitleBlockPluginInterface::setTitle()🤯, right?!
But we'll have to do almost exactly that. We can't change all innards of Drupal.
Work-around: fallback
Unless you are fine with adding fallback rendering! 😄
We could make
ApiPreviewControllerleap ahead of #3491701 slightly and make it assume that it's always used on a content entity route (which is indeed true in XB's UI today):$label_field_name = $entity->getEntityType()->getkey('label')to get the name of the label field$body['entity_form_fields'][$label_field_name . '[0][value]']to get the current entity title->toRenderable()call in fiber execution similar to\Drupal\experience_builder\Plugin\DisplayVariant\PageTemplateDisplayVariant::build()Tada, it works!
Alternative: disallowing
This is maybe possible. Depends on what you mean by "there".
You talked before about how the "main content" block should always be placed in the
contentregion. Can we do the same for the "page title" block? Even though some themes may want to place it somewhere else?Olivero puts it in the
content_aboveregion by default, so this seems like a bad idea?Comment #5
lauriiiI think the solution for "Page title block" and "Main page content block" are different.
The "Main page content block" we should just hide from the XB library completely. I already opened an issue for this #3502372: Hide main page content block from XB.
The "Page title block" we should not hard code to a region because it's not uncommon for a theme to not want to display the page title at all. I assume that it would be more common for people set this in the content type template rather than in the page template. There are some edge cases where this doesn't work but in most cases people wouldn't want to show a title at least on the XB pages.
The proposed fallback seems like a good approach for now. The assumption that XB is always used on a content entity route is acceptable for now but this may change in future, e.g., to allow entering XB using a view page URL.
Comment #6
wim leers👍
P.S.: d'oh, right, I finished writing this while on a meeting — but yes, I intend for #3501600: Split 1 PageTemplate config entity into N PageRegion config entities to hardcode what the
contentPageRegionconfig entity contains: the main block, and nothing else!Comment #7
wim leersComment #8
wim leersComment #9
wim leersComment #10
lauriiiIt looks like we also have an exception in place for the scenario where a title isn't placed. We should get rid of that since we should accept the fact that some sites may not want to place the title block in the page template.
Comment #11
wim leers@lauriii opened #3505872: Page title block should not be required for #10, and I fixed it.
Comment #12
wim leersComment #13
wim leersI think the work-around I articulated 15 days ago at might've become simpler thanks to #3506267: Changing page title doesn't update in the review changes panel.
Comment #14
wim leersWhat I proposed appears to work. This still needs refining though:
Comment #17
wim leersTest coverage in
\Drupal\Tests\experience_builder\Kernel\ApiLayoutControllerPostTestseems like the most feasible approach.Comment #18
f.mazeikis commentedComment #21
wim leersTest coverage looks solid! 👍
@larowlan's review made me realize that once #3512385: Changes to code components are not visible in global regions until published is implemented, a much nicer solution is possible here!
Comment #23
wim leers#3512385: Changes to code components are not visible in global regions until published is in!
Comment #24
wim leersComment #25
f.mazeikis commentedComment #26
wim leersCI-only job fails, proving the test coverage is accurate 👍
Spotted one bug though, which also shows test coverage is not yet complete.
Comment #27
wim leers… and having written #26, I spotted the tag.
I added it in #21. And … that does suggest a better approach that should be possible to implement now: one where
XbPageVariant\Drupal\experience_builder\Controller\ApiLayoutController::getLabel()'s result gets passed into\Drupal\experience_builder\Plugin\DisplayVariant\XbPageVariant::setTitle()😅🙈
Comment #28
wim leersIMHO blocks #3515932: Milestone 1.0.0-beta1: Enable creation of non-throwaway sites because the page title block should work correctly if it is available at all.
Comment #30
f.mazeikis commentedRegarding #27.2:
What about block title shenanigans for situations, where XB is not handling page templates? This currently is the default behaviour on fresh install.
In such situations there are no
Page Regionentities andDrupal\experience_builder\EventSubscriber\PageVariantSelectorSubscriber:: onSelectPageDisplayVariant()returns early instead of selectingXbPageVariantto do rendering.I was playing around with different approach - removing the trait, removing the logic from
ApiLayoutControllerand lettingBlockComponentplugin source handle title block value withtitle_resolverservice. It works, but only on requests with routes that have title resolution. It also breaks some of the tests - haven't dug deep enough to see what's happening, seemed something to do with XbPageVariants cache tags.It's in MR1045 if you want to take a look, although it needs more work and it does add dependencies to
BlockComponentthat I am uncertain about.Should this MR also remove validation from
schema.ymland in-code that prevents users from placing title blocks wherever/however they want on in layout? Or do I misinterpreted #5 and #10?Comment #31
wim leersVery good point — @lauriii in #5 specifically stated:
→ Which means:
XbPageVariant, because that code is only executed if XBPageRegionconfig entities exist ⇒ this means @larowlan's insight linked from #21 actually is not a viable solution, despite being more elegant.Drupal\Core\Block\TitleBlockPluginInterfacefrom theabsencelist ofexperience_builder.content_template.*.*.*(but not of other component trees AFAICT, because it would still make little sense on individual content entities — unless @lauriii says otherwise — assigning to him to get final confirmation of that, #10 does seem to suggest he wants it to be supported everywhere?)
That indeed won't work universally. It's not required for a route to specify
_titleor_title_callback— the render array returned by a controller can also contain the page title. See https://www.drupal.org/node/2067859 and specifically #2359901: Discourage $main_content['#title'] in favor of route titles and title callbacks.So: 👎
Comment #33
f.mazeikis commentedRemoved
Drupal\Core\Block\TitleBlockPluginInterfacefromabsencelist inexperience_builder.content_templateschema, updated tests accordingly.We still need @lauriii to clarify this:
Comment #34
lauriiiBest experience would be to support adding the title block for pages too. How difficult would this be? If this is something that would be difficult to build and/or hard to maintain, we may want to reconsider that.
Comment #35
f.mazeikis commentedDon't thinks it's difficult per se. When you say "pages" - we currently have still have limitations on field component tree and on patterns. I assume you mean we should remove limitation from field component tree? This would allow title blocks to be placed and saved on individual Node layouts.
If so, I would argue that it would make sense to allow patterns to use title block too, as otherwise we're leaving in somewhat convoluted and meaningless restriction.
Removing limitations in general is not difficult, it becomes difficult if there's "granularity and rules" to how limitations should be applied (if the assumptions above are incorrect, for example).
I have also just discovered a bug - with XB not handling templates, we need to figure out how to handle Block title rendering outside of preview.
Comment #36
f.mazeikis commentedComment #37
lauriiiSo let's remove that restriction from both patterns and field component tree 👍
Comment #38
wim leersComment #39
wim leersThis looks great! 👍
Comment #40
wim leersAFAICT
block-form.cy.jsis consistently failing? 🙈😬Seems to have started since
433b7a1e342e19108edcda376726f3fcda1f6502: Unrestric usage of title block. Seems legitimate.Comment #41
f.mazeikis commented@wim-leers The issue seems legitimate, but not related to your suggestion in MR. I'll look into that, but first we need to figure out a way forward out of "Page Title Block isn't rendering, when placed in content region and viewed outside of XB".
We have so many paths for rendering page title block, but not all of them work:
The issue is combination of things:
XbPageVariantonly renders components in fibers, if XB manages templates and there arePageRegionentitiesXbPageVarianthandlescontentregion differently from the rest of the region (we intentionally skip over creatingPageRegionentity for thecontentin the first place)ApiLayoutControllersolves "title block in content region" problem during Preview, by rendering title block in a fiber and providing title valueXbPageVariantskips this region andApiLayoutControlleronly works during Preview in XB (hence the weird conditional inBlockComponent::renderComponent()on line 161We could resolve this by moving "title resolution and value setting" logic out of
XbPageVariantandApiLayoutControllerintoBlockComponent::renderComponent(). This is what "alt approach" MR was trying to achieve. It works, but adds seemingly unnecessary dependencies and complicatesBlockComponentclass for one special case handling.We could also fire some sort of
ComponentBlockPreRenderevent inBlockComponent::renderComponent()- that would allow us to extract this logic into newPageTitleBlockComponentRenderSubscriber. This way the dependencies and logic required for resolving and setting title value could live in it's own isolated subscriber. This would allow us to only concern ourselves with value coming from Title Resolver service for now, as we currently only have limited support for Nodes. Later this be extended by modifying such subscriber or registering additional subscribers for additional routes, without touchingBlockComponentclass itself.This might prove to be a useful mechanism down the line for setting block plugin values before rendering happens for other types of blocks - Views blocks immediately come to mind.
Alternatively, we could try to do "title resolution and value setting" where we render "content" region, but that feels quite iffy.
Is there a simple alternative I am unable to see here?
Is there any particular reason _we have_ to do "alter blocks by rendering them in fibers" bit in XbPageVariant, especially when it comes to title block? Like an upcoming feature I am not aware of that would allow us to "have different Page variants with different titles"?
We could also do the easiest solution and add the validation that returns us to "you can place title block in content region and XB doesn't crash, but you can't save that". Personally, not a fan of doing that.
Comment #43
penyaskitoI like
ComponentBlockPreRenderevent.We might need this for blocks that we don't support yet aside of TitleBlock. E.g. those with lazy render placeholders?
Might be used too in #3485502: [needs design] Handle block contexts (aka implicit inputs vs `settings` aka explicit inputs) for setting the context values when we support context aware blocks.
Or even a more general
ComponentSourcePreRenderevent, because this might apply to other component sources, not only blocks.Comment #44
effulgentsia commentedBy the way, Layout Builder doesn't let you add the page title block either, with an @todo pointing to #2938129: PageTitle block is non-functional when not handled directly by \Drupal\block\Plugin\DisplayVariant\BlockPageVariant.
Personally, I don't think we need to support the page title block until that issue is fixed. Instead, I think we can:
drupalSettings. E.g., perhaps via a hook_preprocess_html()? Then code components have access to it easily, so you could just implement a code component that does<h1>{drupalSettings.pageTitle}</h1>or similar.Then, when #2938129: PageTitle block is non-functional when not handled directly by \Drupal\block\Plugin\DisplayVariant\BlockPageVariant is resolved, we can make the page title block available for people who really want to do it as a block, though if the above two options are available, what's the point of the block?
Just my 2 cents.
Comment #45
lauriii+1 that we should not stick to the current page title block if it turns out to be more work than working around it would be.
Comment #46
wim leers@f.mazeikis in #41: .6 and .7 — ouch, great finds! 😬 This clearly indicates missing test coverage for that. Let's add those.
Points 6 and 7 are impossible to solve using the current mechanisms, because a controller returning
['#title' => 'something']literally has not yet been executed, because the XB field is rendering the page title block before the main content has been rendered:I worry about the performance impact of this — one such event fired per component instance could result in hundreds of events fired for complex component trees 😅
🤔 Why would we want/need to do that? What's really needed for views blocks (that show something contextually relevant, e.g. all related news articles) do so using the context system. For that, we have #3485502: [needs design] Handle block contexts (aka implicit inputs vs `settings` aka explicit inputs).
That reason is
\Drupal\Core\Display\PageVariantInterface::setTitle(), which itself is necessary because of controllers being allowed to return render arrays with#titlein their root, and that will override the_title_resolverresult of the associated route 😬 See the relevant CR.@penyaskito in #43: RE
#lazy_builders for block plugins: see #3518656, and #3518656-12: Add Dynamic Page Cache and BigPipe support: `block` ComponentSource does not use placeholders/#lazy_builder for my outline of a plan for how to support that.@effulgentsia in #44: wow, TIL about #2938129! And glad your mind is also going to (new) prop sources 🤓
Thinking… 🤔
Comment #47
wim leersNote: the original scope was to just make the page title block work in the places where it can be placed in HEAD.
But @lauriii requested the scope to be expanded. I'm still not really convinced why it's a good idea to place the page title block in e.g. a landing page (
xb_pagecontent entity) or an article. IMHO this likely means the page title will appear twice — because if it ever is placed in a landing page, then essentially every content template will have to place it, too. Which … I guess is the very intent/point, to have that freedom. It kinda makes sense, but it seems debatable.In any case: updating the issue title to reflect the broader scope.
Comment #48
wim leers@penyaskito's
#lazy_builderremark in #43 gave me an idea 🤓#lazy_builders.#lazy_builders, then we should be able to make it work.#placeholder_strategy_denylist: https://www.drupal.org/node/3511562Comment #49
wim leersMaking the lazy builder preview-aware fixes 50% of the scenarios in the XB preview:
And making
ApiLayoutControllerpass the current entity title (auto-saved or not) results in 100% working:Comment #51
wim leersReflecting that @f.mazeikis is reviewing & working on making tests pass :)
Comment #52
wim leersMet with @f.mazeikis and dug in to the remaining challenges. We agreed there's 2 remaining steps:
\Drupal\Tests\experience_builder\Kernel\Plugin\Field\FieldType\ComponentTreeItemListTest::provider():— essentially reusing
$component_tree_with_single_block_componentbut changing it to use the page title block instead.This will require an addition like this to
\Drupal\Tests\experience_builder\Kernel\Plugin\Field\FieldType\ComponentTreeItemListTest::testHydrationAndRendering():XbPageVariant:hook_display_variant_plugin_info_alter()to override the class used for theblock_pagepage variant toXbBlockPageVariant::classOnce those 2 things are done, let's land this!
Pre-emptively RTBC'ing because I'll be out in the next 2 days.
Comment #53
f.mazeikis commentedI've implemented things we discussed with Wim and he outlined in #52.
All of this sounded really reasonable, but I've hit some issues.
ComponentTreeItemListTestchecks if setting a value toBlockComponent::$finalTitleresults in that value being used for page title block component. What it doesn't test is whetherBlockComponent::$finalTitleis always initialised and has value without us doing so on Line 122 ofComponentTreeItemListTest.lazy_buildermakes a callback for the page title block component it gets re-rendered instead of being served from cache and this time (and all the following attempts) result in exceptionTyped static property Drupal\experience_builder\Plugin\ExperienceBuilder\ComponentSource\BlockComponent::$finalTitle must not be accessed before initialization. This happens because placeholder is being re-rendered in isolation, without invokingBlockPageVariantsor any other code that would setBlockComponent::$finalTitlevalue. This doesn't seem to happen for fully rendered page visited by anonymous users - see the video.Screen recording of the Exception
I've tried updating to Drupal 11.2 in order to see if using
'#placeholder_strategy_denylist' => [BigPipeStrategy::class => TRUE],would resolve this problem - it did not. The same error happens in the same way, it's just that rendering of placeholder in isolation happens via Drupal Core renderer and not through BigPipe.We could add our best attempt to resolve page title via
title_resolverservice to theBlockComponent::renderPageTitleBlockwith fallback to empty string if it fails.Otherwise I am not quite sure what are our options, until #3370946, #2403359, #2359901 and #2938129 are resolved. As it seems that the majority of our problems here are related to Page Title Block not resolving page title by itself and instead relying on page title value to be resolved and set elsewhere, like in
BlockPageVariant.@effulgentsia Suggested disallowing the use of page title block and switching to a title code component that uses title set in
drupal.Settings, but I wonder if we would hit exact same set of problems where we need to resolve the page title value in order to be able to pass it todrupal.Settings.Comment #54
wim leersThe test you added in https://git.drupalcode.org/project/experience_builder/-/merge_requests/7... looks solid. 👍
I’ll review the new challenges you identified in #53 on Monday. (Thanks for the detailed comment, that is very appreciated!)
Just move on to a different issue, I don’t want you to have nightmares about this over the weekend 🧟 😅
Comment #55
wim leersThis kinda belongs more under #3520484: [META] Production-ready ComponentSource plugins, so moving it there.
Comment #56
wim leersComment #57
effulgentsia commentedI think this is close to landing, so whether or not it's tagged as a "beta blocker" is probably moot, but I'm untagging it per #44.
Comment #58
wim leersℹ️ #3531249: Populate data in `drupalSettings` to enable simple use cases in dynamic code components landed, so now this issue should update the page title bits that introduced.
Comment #60
kristen polStill getting these:
"page_title_block block plugin does not support previews"
in the logs
Comment #61
kristen polMR needs updating as patch fails
Comment #62
wim leers@kristen pol: yes, that is known.
Unfortunately, shortly after this was assigned to me to try to overcome the last obstacles identified by Felix, this was deprioritized in favor of higher-priority issues. Unassigning.
Comment #63
penyaskitoFor awareness: #3561564: Support the same block being used for page title & main content
Comment #64
wim leers