Skip to content

Block Editor: Implements hints for Tabs (fixes #21178)#21672

Open
nielslyngsoe wants to merge 10 commits intomainfrom
v17/bugfix/21178
Open

Block Editor: Implements hints for Tabs (fixes #21178)#21672
nielslyngsoe wants to merge 10 commits intomainfrom
v17/bugfix/21178

Conversation

@nielslyngsoe
Copy link
Member

Implements hints for tabs of the Block Workspace. Notice how this is used for all Blocks, in the Modal when editing Blocks, and when editing List Blocks in inline-mode.

This generally finished the implementation of badges, it seems there where a few missing links/bindings between the View Contexts.

Here a few screenshots of the scenarios I have tested:

image image image

Copilot AI and others added 10 commits December 22, 2025 10:15
Co-authored-by: nielslyngsoe <6791648+nielslyngsoe@users.noreply.github.com>
Co-authored-by: nielslyngsoe <6791648+nielslyngsoe@users.noreply.github.com>
# Conflicts:
#	src/Umbraco.Web.UI.Client/src/packages/block/block/workspace/views/edit/block-workspace-view-edit-content-no-router.element.ts
#	src/Umbraco.Web.UI.Client/src/packages/block/block/workspace/views/edit/block-workspace-view-edit.element.ts
Copilot AI review requested due to automatic review settings February 6, 2026 20:19
@nielslyngsoe nielslyngsoe enabled auto-merge (squash) February 6, 2026 20:19
@nielslyngsoe nielslyngsoe changed the title (fixes #21178) Block Editor: Implements hints for Tabs (fixes #21178) Feb 6, 2026
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Implements tab-level hint/badge support in the Block Workspace by introducing per-tab view contexts and wiring validation hints into those contexts so tab headers can display badges when a tab contains validation issues.

Changes:

  • Add per-tab UmbViewController instances in block workspace edit views and render tab badges based on firstHintOfVariant.
  • Switch block element managers from UmbHintContext to UmbViewContext and route validation hints through view.hints.
  • Wire block workspace content/settings views to inherit from the workspace view (but includes a stray debug subscription).

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 6 comments.

File Description
src/Umbraco.Web.UI.Client/src/packages/block/block/workspace/views/edit/block-workspace-view-edit.element.ts Creates/provides per-tab view contexts and renders hint badges in router-based block edit tabs.
src/Umbraco.Web.UI.Client/src/packages/block/block/workspace/views/edit/block-workspace-view-edit-content-no-router.element.ts Adds similar per-tab view contexts + hint badge rendering for inline/no-router block editing.
src/Umbraco.Web.UI.Client/src/packages/block/block/workspace/block-workspace.context.ts Hooks up view inheritance for content/settings views (but also adds a debug console subscription).
src/Umbraco.Web.UI.Client/src/packages/block/block/workspace/block-element-manager.ts Replaces hint context with view context and connects validation-to-hints to view.hints.
Comments suppressed due to low confidence (1)

src/Umbraco.Web.UI.Client/src/packages/block/block/workspace/views/edit/block-workspace-view-edit-content-no-router.element.ts:71

  • When _hasRootProperties changes, you don’t call #setupViewContexts(). If root properties become available after initial load, the required view context (and its hint observer) will never be created. Call #setupViewContexts() from the hasProperties observer as well.
		this.observe(
			this.#tabsStructureHelper.hasProperties,
			(hasRootProperties) => {
				this._hasRootProperties = hasRootProperties;
				this.#checkDefaultTabName();
			},
			'observeRootProperties',
		);


if (viewAlias === null) {
// for the root tab, we need to filter hints
view.hints.setPathFilter((paths) => paths[0].includes('tab/') === false);
Copy link

Copilot AI Feb 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The path filter assumes paths[0] exists. With the new hint path prefix of [] in the block element manager, root properties (no container) can produce hints with an empty path, causing paths[0].includes(...) to throw. Use a safe check (e.g., handle empty paths as “not in a tab”).

Suggested change
view.hints.setPathFilter((paths) => paths[0].includes('tab/') === false);
view.hints.setPathFilter((paths) => {
const firstPath = paths[0];
// Treat empty paths as "not in a tab", so they belong to the root tab
if (!firstPath) {
return true;
}
return firstPath.includes('tab/') === false;
});

Copilot uses AI. Check for mistakes.

if (viewAlias === null) {
// for the root tab, we need to filter hints, so in this case we do accept everything that is not in a tab: [NL]
view.hints.setPathFilter((paths) => paths[0].includes('tab/') === false);
Copy link

Copilot AI Feb 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The root-tab hint filter assumes paths[0] exists. Root-property validation hints can have an empty path (no container), which will make paths[0].includes(...) throw at runtime. Please guard for empty paths (treat them as root/non-tab hints).

Suggested change
view.hints.setPathFilter((paths) => paths[0].includes('tab/') === false);
view.hints.setPathFilter((paths) => {
const firstPath = paths[0];
// Root-property validation hints can have an empty path (no container), so treat them as root/non-tab hints.
if (!firstPath) {
return true;
}
return firstPath.includes('tab/') === false;
});

Copilot uses AI. Check for mistakes.
Comment on lines +87 to +90
this.view.hints.hints.subscribe((hints) => {
console.log('workspace', hints);
});

Copy link

Copilot AI Feb 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove the debug subscription/console.log. The subscribe here is never unsubscribed (potential leak) and will spam the console in production builds.

Suggested change
this.view.hints.hints.subscribe((hints) => {
console.log('workspace', hints);
});

Copilot uses AI. Check for mistakes.
Comment on lines 58 to 62
this.observe(this.#tabsStructureHelper.childContainers, (tabs) => {
this._tabs = tabs;
this.#checkDefaultTabName();
this.#setupViewContexts();
});
Copy link

Copilot AI Feb 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

#setupViewContexts() can run before the block workspace context has been consumed (and before #blockManager is set). Since #createViewContext throws when #blockManager is missing, this can crash during initial render. Guard #setupViewContexts/#createViewContext until #blockManager is available, or call #setupViewContexts only after the context callback runs.

Copilot uses AI. Check for mistakes.
Comment on lines +99 to +100
// Create view contexts for root groups
if (this._hasRootGroups) {
Copy link

Copilot AI Feb 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The root tab is rendered when _hasRootGroups || _hasRootProperties, but #setupViewContexts() only creates the root view context when _hasRootGroups is true. This means root-property hints (and default root selection) won’t work when there are root properties but no root groups. Create the root view context when _hasRootGroups || _hasRootProperties.

Suggested change
// Create view contexts for root groups
if (this._hasRootGroups) {
// Create view contexts for root groups/properties
if (this._hasRootGroups || this._hasRootProperties) {

Copilot uses AI. Check for mistakes.
Comment on lines 142 to 159
#checkDefaultTabName() {
if (!this._tabs || !this.#blockWorkspace) return;

// Find the default tab to grab
if (this._activeTabKey === undefined) {
if (this._hasRootGroups || this._hasRootProperties) {
this._activeTabKey = null;
const context = this.#tabViewContexts.find((context) => context.viewAlias === null);
if (context) {
this._activeTabKey = null;
this.#provideViewContext(null);
}
} else if (this._tabs.length > 0) {
const tab = this._tabs[0];
this._activeTabKey = tab.ownerId ?? tab.ids[0];
if (tab) {
this._activeTabKey = tab.ownerId ?? tab.ids[0];
this.#provideViewContext(getViewAliasForTab(tab));
}
}
Copy link

Copilot AI Feb 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

#checkDefaultTabName() now depends on #tabViewContexts being populated, but it runs before #setupViewContexts() (and #setupViewContexts() doesn’t re-run #checkDefaultTabName() afterward). This can leave _activeTabKey undefined so no tab content renders until user interaction. Create view contexts before choosing defaults, or re-run default selection after contexts are created.

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants