Skip to content

Elements: Contextualize variant blocks rendering for invariant content - #22790

Merged
kjac merged 4 commits into
v18/feature/reusable-block-contentfrom
v18/feature/rendering-variant-blocks-for-invariant-content
May 12, 2026
Merged

Elements: Contextualize variant blocks rendering for invariant content#22790
kjac merged 4 commits into
v18/feature/reusable-block-contentfrom
v18/feature/rendering-variant-blocks-for-invariant-content

Conversation

@kjac

@kjac kjac commented May 11, 2026

Copy link
Copy Markdown
Contributor

Prerequisites

  • I have added steps to test this contribution in the description below

Description

We currently have issues contextualizing blocks of variant element types in an invariant context. While we have tests proving it works, these are actually false positives; it only works for the first request... then the cache takes over, and continues to serve the initially accessed variant for all requested languages.

This PR amends that shortcoming.

Testing this PR

You'll need to use the Management API here, because block editors do not (yet) support the use of variant element types in invariant content:

image
  1. Create an invariant and a variant element type.
  2. Create elements of both types in the library.
  3. Configure a block list data type to use both element types.
  4. Create an invariant document type with a block list property using this data type.
  5. Use the Management API to create and publish a document with a mix of blocks based on both invariant and variant element types.
  6. Render the document in multiple languages and verify that the rendering contextualizes the element output correctly.

Breaking change

This changes the signature of the public interface IBlockElementService, and that is a breaking change. Unfortunately, it is not possible to create any meaningful overload to retain backwards compatibility, because it would require either:

a. Handling a nullable owner parameter in the service implementation, which kinda defeats the purpose, or
b. New'ing up a PublishedElement instance to pass as default, which is a super complex entity, filled with a lot of inner workings.

Since IBlockElementService will be introduced in V18, and the target PR for this PR is slated for V19, the impact of this breakage will be quite limited, so I think we should just accept it. Also, the target PR will probably be breaking in itself anyway 🤷

@kjac
kjac marked this pull request as ready for review May 11, 2026 05:50
@claude

claude Bot commented May 11, 2026

Copy link
Copy Markdown

Claude finished @kjac's task in 4m 28s —— View job


PR Review

Target: origin/v18/feature/reusable-block-content · Based on commit: b37d8788

Fixes a cache poisoning bug where variant block elements in invariant content rendered correctly on the first request, then served the initially-cached culture for all subsequent requests. The fix threads the owning IPublishedElement through to BuildElementAsync, detects the invariant-owner/variant-element mismatch, and replicates the default-language property value across all languages before building the cache node.

  • Modified public API: IBlockElementService.BuildElementAsync — new required IPublishedElement owner first parameter
  • Affected implementations (outside this PR): Any external code that implements IBlockElementService directly (none in src/)
  • Breaking changes: BuildElementAsync signature changed without backwards-compat mitigation — compile error for external implementors

Important

  • src/Umbraco.Core/PublishedCache/IBlockElementService.cs:8: BuildElementAsync adds a required owner parameter without a Pattern-2 or Pattern-3 backwards-compat mechanism. Per CLAUDE.md §5 any public interface/method signature change requires retaining the old signature. The PR description explains the difficulty, but the repo policy still applies — even a Pattern-3 default that throws is better than a silent compile error for external implementors. A workable approach: add an obsolete default implementation that delegates to the new overload using a minimal invariant-stub owner, preserving the new (correct) behaviour for legacy callers:

    // Internal helper — not exposed in the public namespace
    internal sealed class InvariantPublishedContentTypeStub : IPublishedContentType
    {
        // implement only what VariesByCulture() reads: Variations = ContentVariation.Nothing
        public ContentVariation Variations => ContentVariation.Nothing;
        // remaining members throw NotImplementedException
    }
    
    internal sealed class InvariantOwnerStub : IPublishedElement
    {
        public static readonly InvariantOwnerStub Instance = new();
        public IPublishedContentType ContentType { get; } = new InvariantPublishedContentTypeStub();
        // remaining members throw NotImplementedException
    }

    Then in the interface:

    [Obsolete("Use BuildElementAsync(IPublishedElement, BlockItemData, bool?) instead. " +
              "Scheduled for removal in Umbraco 20.")]
    Task<IPublishedElement?> BuildElementAsync(BlockItemData blockItemData, bool? preview = null)
        => BuildElementAsync(InvariantOwnerStub.Instance, blockItemData, preview);

    This keeps external callers compiling while giving them the corrected behaviour. Fix this →

Suggestions

  • src/Umbraco.PublishedCache.HybridCache/Services/BlockElementService.cs:61: allLanguages.Single(l => l.IsDefault) throws InvalidOperationException if no language is marked as default (shouldn't happen in a correctly configured system, but defensive code here is cheap). Consider SingleOrDefault() with an early-return null guard.

  • tests/Umbraco.Tests.Integration/Umbraco.PublishedCache.HybridCache/PropertyValueLevelDetectionTests.cs:106: The new mock owner (invariant ContentVariation.Nothing) is correct for the updated signature. Worth noting: the test element type (umbElement) has only invariant properties, so the new special-case branch in BlockElementService is not exercised here. That path is covered by BlockListElementLevelVariationTests.Parsing.cs, so no coverage gap — just worth knowing where the new logic is actually tested.


Request Changes

The breaking-change pattern violation is an Important issue that should be addressed before merge. The fix logic itself is correct and well-tested — the dual-culture assertion loop in the integration tests (AssertBlockValues() called once per language) cleanly proves the cache-poisoning regression is closed. A backwards-compat shim on the interface is the only thing needed before this is ready.

Comment thread src/Umbraco.PublishedCache.HybridCache/Services/BlockElementService.cs Outdated
Comment thread src/Umbraco.PublishedCache.HybridCache/Services/BlockElementService.cs Outdated
Comment thread src/Umbraco.Core/PublishedCache/IBlockElementService.cs
@kjac kjac changed the title Contextualize variant blocks rendering for invariant content Elements: Contextualize variant blocks rendering for invariant content May 11, 2026

@AndyButland AndyButland left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Updates all look good to me. We'll do pair review of the before and after later.

@kjac
kjac merged commit 0189c02 into v18/feature/reusable-block-content May 12, 2026
9 of 10 checks passed
@kjac
kjac deleted the v18/feature/rendering-variant-blocks-for-invariant-content branch May 12, 2026 08:23
leekelleher added a commit that referenced this pull request Jun 23, 2026
* Slice 1: Reference Model

A block layout item's contentKey can point to either local inline
content or a library element. For shared content, the `isSharedContent`
flag is set to `true`.

* Slice 2: Insert Block from Library

* Slice 3: Transfer to Library

* Slice 4: Disconnect from Library

* Slice 5: Inline Element Editing from Block Context

* [WIP] Slice 6: Publish Awareness

* fix(block): address code review findings

Critical:
- disconnectFromLibrary now sets initial expose for new local content
  and cleans up resolved variant state entry
- Extract #updateExposedState() in entry elements, called from all three
  observers (hasExpose, isLibraryElement, sharedContentVariantState) to
  prevent stale unpublished state on library blocks

Important:
- Guard #fetchLibraryElement against already-resolved elements to prevent
  redundant server requests
- Hoist UmbElementDetailRepository to class field in entry elements to
  avoid accumulating dead controllers

Suggestion:
- Fix umb-localize key attributes to use literal keys instead of
  resolved strings from localize.term()

* Enable reusable elements in block editors, including indexing for search and output rendering

* Update cache levels for property value converters.

* Add keys to block layout items

* fix(block): address review findings for reusable block content

- Use DocumentVariantStateModel.DRAFT enum instead of magic string
  in both block-list and block-grid entry elements
- Strip isSharedContent from layout during clipboard write to prevent
  pasted blocks from incorrectly appearing as library references
- Guard #setInitialBlockExpose in disconnectFromLibrary against missing
  content type structure
- Store all element variants and resolve against active variantId for
  correct multi-culture state display
- Add already-resolved guard to #fetchLibraryElement
- Add JSDoc on isLibraryElement and sharedContentVariantState observables
- Add .trim() to transfer modal name validation

* feat(block): add layout key and migrate identity from contentKey to key

BREAKING: UmbBlockLayoutBaseModel now requires a `key: string` property.
Plugin code that creates layout objects without `key` will get a compile
error.

- Change UmbArrayState identity functions to use `(x) => x.key`
- Add `layout` setter on entry elements (list, grid, single, rte) that
  extracts both layoutKey and contentKey from the layout object
- Deprecate `contentKey` setter on entry elements (use `layout` instead)
- Add `layoutKey` read-only getter for sorter identity
- Add `setLayoutKey()` / `layoutByKey()` / `getLayoutByKey()` methods
- Update `transferToLibrary` and `disconnectFromLibrary` to take layoutKey
- Update delete operations to find by layout key, only remove shared
  content/settings/exposes if no other layout references the same contentKey
- Migrate grid recursive area operations to use key for identity
- Update `unique` observable on entry context to derive from layout key
- Generate new key on property value clone
- Backwards compat: `setLayouts` assigns `key ??= contentKey` for
  persisted data without key
- Strip `isSharedContent` from clipboard layout clone
- Update sorter configs and repeat key functions

* Add tests proving that reusable content can work with RTEs too

* i18n: capitalize Element/Library in disconnect-from-library strings

Per Niels' feedback on PR #22448 — Element and Library are product nouns
and should be capitalized to distinguish from generic uses.

* refactor(block): rename layout/library APIs for consistency

- `setLayoutKey`/`getLayoutKey` → `setKey`/`getKey` on entry context
- `layoutByKey`/`getLayoutByKey` → `byKey`/`getByKey` on entries context
- `layoutKey` property → `key` on block entry elements (list/grid/single/rte)
- `data-layout-key` attribute → `data-key` on `umb-rte-block`
- `insertLibraryElementReference` → `insertLibraryElement` on manager
- `transferToLibrary`/`disconnectFromLibrary` `layoutKey` param → `key`
- `delete(layoutKey)` param → `delete(key)` on entries context
- `allowedLibraryElementTypeKeys` → `libraryAllowedElementTypeKeys` on catalogue modal data

* refactor(block): convert catalogue modal value to discriminated union

`UmbBlockCatalogueModalValue` was a single object with optional `create`,
`clipboard`, and `library` fields, which let invalid combinations type-check.
Convert it to a true discriminated union so consumers must narrow with `'in'`
before accessing the variant payload.

Update all four entries contexts (block-list, block-grid, block-rte, block-single)
to use `value && 'create' in value` style narrowing in their `onSubmit` handlers.

* refactor(block): move library transfer/disconnect handlers to Block Manager

The block entry elements (`umb-block-list-entry`, `umb-block-grid-entry`) each
duplicated the orchestration for transferring a local block's content to the
Element Library and disconnecting a referenced library element back to local
content. The handlers opened modals, scaffolded element data, called the
element repository, and finally mutated manager state — all from the UI element.

Move that logic to the manager as `requestTransferToLibrary(key)` and
`requestDisconnectFromLibrary(key)`. The "request" prefix marks the
user-confirmed flows; the bare `transferToLibrary` / `disconnectFromLibrary`
methods remain as the pure state mutations.

Entry elements now delegate to the manager, which also lets us drop the
per-element `UmbElementDetailRepository` field and the modal-related imports
from the elements.

Confirm modal headlines/labels are now passed as localization keys, letting
the modal handle its own string resolution (per Niels' review feedback).

* refactor(block): deprecation hygiene around contentKey setters

- Stop calling the (deprecated) `setContentKey()` from `set layout` on the
  entry elements. The layout already carries the contentKey, so internal flows
  no longer need the fallback path.
- Add `UmbDeprecation` runtime warnings to all four `set contentKey` element
  setters (list/grid/single/rte) and to `UmbBlockEntryContext.setContentKey`.
  JSDoc `@deprecated` alone is not enough — runtime warnings are required per
  the Web.UI.Client deprecation policy.

* fix(block): preserve isSharedContent through clipboard copy/paste

When copying a block that references a library Element, we were stripping
`isSharedContent` from the cloned layout so that pasting always produced a
local copy. Per Niels' review feedback, the expected behaviour is the inverse:
a copied library-referencing block should paste as a reference. If the user
wants a local copy after paste, they explicitly disconnect from the library.

- Remove the `delete clonedLayout.isSharedContent` in `#copyToClipboard` for
  both block-list and block-grid.
- Branch in `_insertBlockFromPropertyValue` so layouts with `isSharedContent`
  route through the manager's `insertLibraryElement(contentKey, originData)`
  flow rather than expecting matching `contentData` (which the clipboard
  payload deliberately doesn't carry for references).

* refactor(block): centralise library element resolution in the Block Manager

Per Niels' review: the entry context shouldn't be the place where the safety
fetch for library element content lives — there could be other call sites,
and the manager already owns the resolved-elements state.

Add a layouts observer in `UmbBlockManagerContext` that watches `_layouts`
and, for any layout where `isSharedContent` is set, kicks off
`#fetchLibraryElement(contentKey)`. The fetch already dedupes, so this is
safe to call repeatedly.

In return, drop both `_manager.ensureContentResolved(contentKey)` calls from
`UmbBlockEntryContext` (`setContentKey` and `#observeContentData`). Also
derive `#contentKey` from the observed layout so internal flows have access
to it without callers having to push it through the deprecated setter.

* docs(block): add follow-up TODOs from review

- Mark `#fetchLibraryElement` for `@madsrasmussen` to replace with a batching
  manager that bundles multiple element requests into a single round-trip.
  Today's per-key fetch becomes N+1 on pages with many shared blocks.
- Update the catalogue modal TODO to reflect that the catalogue is conceptually
  a Modal/Flow extension point — not a Workspace as the previous comment
  implied. Captures the open question about an extensible "Library tab"
  surface for other content sources.

* fix(block): break circular dep between manager context and modals barrel

`UmbBlockManagerContext` imported `UMB_BLOCK_TRANSFER_TO_LIBRARY_MODAL` from
`../modals/index.js` (the barrel). That barrel transitively pulled in the
catalogue modal element, which sits downstream of the manager — creating:

  context/index → block-manager.context → modals/index
    → modals/block-catalogue/index → block-catalogue-modal.element

Import the token directly from `transfer-to-library-modal.token.ts` instead.

* Elements: Contextualize variant blocks rendering for invariant content (#22790)

* Contextualize variant blocks rendering for invariant content

* Initialize local language variables in a more readable way

* Also filter out whitespace cultures

* Add XML docs.

* feat(block): migrate library transfer/disconnect to blockAction extensions

The `#renderTransferToLibraryAction()` / `#renderDisconnectFromLibraryAction()`
render methods (and their handlers) were commented out when `main` was merged
in, leaving these flows unrendered. Migrate them to the new `blockAction`
extension type (PR #22459) so they:

- Render through `<umb-block-action-list>` like the other common actions.
- Reuse automatically across Block List, Block Grid, Single Block, and RTE
  Block editors — no per-editor code.
- Honour visibility via manifest conditions, not inline state branches.

Additions:
- `UMB_BLOCK_ENTRY_IS_LIBRARY_ELEMENT_CONDITION` — boolean-match condition
  observing the existing `context.isLibraryElement` observable. Used inverted
  by the two new actions.
- `Umb.BlockAction.TransferToLibrary` (weight 250, `icon-link`) — visible when
  `isLibraryElement` is false and the entry is not read-only.
- `Umb.BlockAction.DisconnectFromLibrary` (weight 250, `icon-unlink`) — visible
  when `isLibraryElement` is true and the entry is not read-only. The two
  actions are mutually exclusive so sharing a weight is safe.
- Two thin proxy methods on `UmbBlockEntryContext`
  (`requestTransferToLibrary()` / `requestDisconnectFromLibrary()`) mirroring
  the established `requestDelete()` pattern — actions consume only the entry
  context and call into the manager via these proxies.

Removals:
- The commented-out `#renderTransferToLibraryAction` /
  `#renderDisconnectFromLibraryAction` blocks and their handlers in
  `block-list-entry.element.ts` and `block-grid-entry.element.ts`.

* Renamed `sharedContentVariantStateOf` to `elementStateOf`

* TODO comments and prettify

* Removed `ensureContentResolved`

turns out it was redundant.

* Inlined `transferToLibrary` and `disconnectFromLibrary`

Both were single-use imperative helpers called only by their respective
`request*` counterparts in the same file, with no external callers. The
"request" / "do" split was speculative; folding them in reduces surface
area and matches the recent `ensureContentResolved` cleanup.

* Lifted library-allowed element-type fetch to base entries context

All four block variants (list, grid, rte, single) had the same six-line
block fetching the element-type uniques that overlap with the block
types. Moved into a protected helper `_getLibraryAllowedElementTypeKeys`
on UmbBlockEntriesContext so each variant just calls it.

* Removed `@property` decorator from `layout` setter

The setter had no matching getter, which Lit warns about (and will error
on in a future version) for reactive properties. Since no render template
reads `this.layout` and the setter's effects flow through the entry
context's own observables, the reactive tracking is unused — dropping the
decorator silences the warning without behaviour change.

Consumers using `.layout=${x}` in Lit templates are unaffected; that's
property assignment, not attribute reflection, and doesn't require the
property to be reactive.

* feat(components): adds `umb-entity-frame` component + Storybook stories

Cherry picked from PR #22844

* Fixed block delete passing contentKey where layout key is required

`UmbBlockEntriesContext.delete()` was changed earlier on this branch to
take the layout `key` (so that multiple layouts referencing one shared
contentKey can be deleted independently). Two callers still passed
`contentKey`, which made `delete` throw "Cannot delete block, missing
layout for X" the moment a user tried to remove a block:

- `UmbBlockEntryContext.delete()` — fires on user delete from the UI.
- `block-workspace.context.ts` modal-rejected handler — fires when
  cancelling a brand-new block in live-editing mode.

Both now pass the layout key.

* Added `umb-entity-frame` to Block editor entry UI

Adds `--umb-color-reference` and `--umb-color-reference-contrast` CSS variables

* 🧹 Linting

* Block Single: derive `_exposed` from library element variant state

Aligns block-single-entry with block-list-entry and block-grid-entry:
library-element references now compute their unpublished/draft state
from the shared element's variant state instead of the (always-missing)
expose entry. Without this, inserted Library Elements always appeared
as Draft in single-block editors.

Also sets the `is-reference` attribute when the block is a library
reference, which activates the existing `:host([is-reference])` styles.

* Block entries: collapse `_isReferenceAttr` into `_isLibraryElement`

The two fields were always set together to the same value across all
three entry elements. `_isReferenceAttr` existed only because `@state`
doesn't reflect to an HTML attribute. Decorating the existing
`_isLibraryElement` field with `@property({ attribute: 'is-reference',
reflect: true })` covers both jobs — it reflects to the attribute (for
the existing `:host([is-reference])` CSS) and is still read from JS by
`#updateExposedState()`.

* Fix build errors after merges

* Refine block-catalogue-modal Library tab

- Convert _hasLibraryElements from @State() to native private field
  (set once in connectedCallback before first render; no reactivity needed)
- Promote inline .props object to #libraryTreeProps class field
  (stable reference avoids re-setting umb-tree props on every render)
- Remove self-documenting comment from #librarySelectableFilter
- Remove stale TODO comment

* Wire Library tab search in block-catalogue-modal

- Route tree selection through pickerContext.selection (unified path with
  search-result selections; removes direct writes to this.value from tree handlers)
- Observe pickerContext.selection.selection to drive this.value
- Observe pickerContext.search.query to hide tree while a search is active
- Configure selection as single-select (setMultiple(false))
- Pass selectionManager to tree props for visual selection state
- Add Umb.PickerSearchResultItem.Element manifest and element under
  src/packages/elements/picker/ so search results render correctly

* Backoffice: Simplify insertLibraryElement in block-manager.context

Library elements do not need an expose entry — exposure is derived from
the element's own variant state. Remove the redundant fetchLibraryElement
call and expose-setting logic; the layout observer already handles the
fetch automatically when the layout is appended.

* Backoffice: Rename transfer-to-library to transfer-to-element-library

* Sets the Entity Frame color for non-references

* "Transfer to Library" modal updates

Pre-populates the name field.

* Backoffice: Rename disconnect-from-library to disconnect-from-element-library

* Checks published visibility for Block entry items

+ markup tweaks

* Backoffice: Fix block showing as unsupported after Transfer to Element Library

After a transfer the manager assigns a new UUID (created.unique) to the
layout's contentKey. The entry context was not re-observing content for
the new key, leaving it permanently watching the old (now-gone) content.

Two interacting issues:

1. #observeContentData() was never re-called when layout.contentKey
   changed — only when the layout key itself changed or the manager
   first connected.  A new observer on this.contentKey now re-calls it
   on every contentKey change, with this.#contentKey synced first
   (because #observeLayout() assigns it AFTER _layout.setValue() emits,
   so downstream callbacks would otherwise read the stale value).

2. The guard 'if (unsupported !== true)' permanently locked the flag once
   it was set by the transient {content:undefined, isLibrary:false}
   emission during the transfer.  Replaced with #structurallyUnsupported
   — only set by #getContentStructure / #observeBlockType when the block
   type or element type is genuinely absent — so the content observer can
   freely reset the flag for all other transitions including transfer.

* Block Single: CSS selector fix

* Backoffice: Show link icon in block entry tabs for library elements

When a block is transferred to the Element Library (a shared element), add a
<uui-icon name="link"> to the entity-frame tab to make the library/shared
status visually clearer alongside the existing purple colour theme.

Applies to block-list, block-grid, and block-single entry components.
The icon is shown conditionally when _isLibraryElement is true.

* `requestTransferToElementLibrary` removed the `name` parameter

as can be retrieved from the context itself.

* fix(block): make block action href and validation data path reactive

`umb-block-action.element.ts` previously resolved `getHref()` and
`getValidationDataPath()` once in the `api` setter via `.then()`,
freezing the values for the lifetime of the action component. When a
block's `contentKey` changes at runtime (e.g. after disconnecting from
the Element Library), the edit button kept navigating to the stale path.

Add optional `hrefObservable` and `validationDataPathObservable` to
`UmbBlockAction`. When an action provides these observables the element
subscribes to them reactively; otherwise it falls back to the existing
one-shot promise path (non-breaking for third-party actions).

`UmbEditContentBlockAction` now observes `workspaceEditContentPath` and
`contentKey` from the block entry context and pushes updates into states,
resolving the stale-href bug on disconnect.

Resolves the [LK] TODO in block-action.element.ts.

* fix(block): refresh expose observer after disconnect from element library

After "Disconnect from Element Library" the block's layout.contentKey
changes from the shared element's UUID to a fresh local content key. The
expose observer ('observeExpose' in #gotVariantId) was bound to the old
key and never re-bound because #gotVariantId only runs when variantId
changes, not when contentKey changes — leaving _hasExpose false and the
block showing a stale "Draft"/unpublished badge.

Re-running #gotVariantId alongside #observeContentData in the contentKey
observer ensures the expose subscription always targets the current key,
mirroring the existing pattern already applied for the content observer.

* fix(block): correct workspace tabs and submit label for library elements

When a block references a Library Element (isSharedContent: true), opening
the block workspace via the "Edit Settings" action now shows only the
"Settings" tab. The "Content" tab is hidden because the content is owned
by the shared element and is not editable in the local block workspace.

Surfaces `hasContent` on the block workspace context, and gates
the Content workspace view on a new `Umb.Condition.BlockWorkspaceHasContent`
condition — mirroring the existing `Umb.Condition.BlockWorkspaceHasSettings`
pattern. Also removes the dead `TODO_conditions` block from the Content
view manifest.

* refactor(block): rename LibraryElement to SharedContent for naming consistency

Aligns block symbols that describe a block's content being shared/referenced
with the existing 'isSharedContent' layout flag and 'sharedContentVariantState',
retiring the inconsistent 'LibraryElement' naming for that concept.

- Entry state: isLibraryElement -> isSharedContent; #libraryElementWorkspacePath
  -> #sharedContentWorkspacePath; the three entry elements' _isLibraryElement
  -> _isSharedContent.
- Manager: insertLibraryElement -> insertSharedContent; #fetchLibraryElement
  -> #fetchSharedContent; #resolvedLibraryElements(Variants)
  -> #resolvedSharedContent(Variants).
- Condition: UmbBlockEntryIsLibraryElementCondition
  -> UmbBlockEntryHasSharedContentCondition (alias 'Umb.Condition.BlockEntryHasSharedContent').
- Route segment 'library-element' -> 'library'.

Genuine Element Library feature references are intentionally kept: the
transfer/disconnect actions and modals, and the catalogue picker UI
(#hasLibraryElements, #renderLibrary, blockEditor_tabLibrary, the
{ library: { elementKey } } modal value, libraryAllowedElementTypeKeys).

Pure rename, no behaviour change.

* fix(block): address PR review feedback on client-side files

- block-catalogue-modal: add UmbDeselectedEvent import; correctly type
  #onLibraryElementDeselected parameter (was UmbSelectedEvent)
- block-catalogue-modal: fix #librarySelectableFilter to handle
  undefined documentType.unique via nullish coalesce
- block-manager: setLayouts no longer mutates incoming layout objects
  in-place; uses map+spread to ensure backwards-compat key backfill
  without side effects on the caller's array
- block-grid-to-block-copy-translator: clipboard layout key now uses
  gridLayout.key (layout identity) rather than gridLayout.contentKey,
  which would break when the same shared-content element appears in
  multiple layout entries

* Fix low-hanging PR review comments

* Clarify why top-level aggregation works in effect

* Rename IsSharedContent (server-side)

* Rename IsSharedContent (client-side)

to `IsExternalContent`

* Added comments to clarify retries in tests

* Replace "isSharedContent" with "isExternalContent"

* Block: address review feedback — naming, comments, and small refactors

- requestTransferToElementLibrary / requestDisconnectFromElementLibrary → requestTransferToExternalContent / requestDisconnectFromExternalContent (manager + entry context + action callers)
- .addAdditionalPath('library') → 'element'
- #resolvedExternalContent / #resolvedExternalContentVariants → #externalContentValues / #externalContentVariants
- elementStateOf → externalContentStateOf
- hrefObservable / validationDataPathObservable → href / validationDataPath (interface + action impls + default kind element)
- _hasExpose → _localExpose (grid, list, single entry elements)
- BlockWorkspaceHasContentConditionConfig / BlockEntryHasSettingsConditionConfig: type alias → interface
- Remove implementation-specific / AI-ish comments from block-entry, block-manager, action files, block-workspace
- Reuse #elementRepository field in requestTransfer/Disconnect; remove local instantiations
- #fetchExternalContent now accepts an array — one call per layout-state update instead of N
- getHref / getValidationDataPath in edit-content/edit-settings actions now resolve via the observable

* Block: fix CI lint errors — remove unused #context fields and suppress empty-interface rule

---------

Co-authored-by: leekelleher <leekelleher@gmail.com>
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