feat(components): adds umb-entity-frame component + Storybook stories - #22844
Conversation
|
Claude finished @leekelleher's task in 3m 35s —— View job PR ReviewTarget: Adds a new
Important
Suggestions
Approved with Suggestions for improvementGood to go, but please carefully consider the |
This comment was marked as outdated.
This comment was marked as outdated.
There was a problem hiding this comment.
Pull request overview
This PR introduces a new core UI building block, <umb-entity-frame>, intended to visually distinguish referenced “global elements” from local blocks by drawing a framed overlay and label tab around its parent container.
Changes:
- Adds the
umb-entity-frameweb component (Lit) with alabelfallback and CSS custom properties for opacity/color/border width. - Exposes the component through the core components barrel export.
- Adds Storybook stories (multiple usage variants) and a basic unit test suite including optional a11y audit.
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| src/Umbraco.Web.UI.Client/src/packages/core/components/index.ts | Exports the new entity-frame component from the core components barrel. |
| src/Umbraco.Web.UI.Client/src/packages/core/components/entity-frame/index.ts | Adds the component entrypoint re-export/registration. |
| src/Umbraco.Web.UI.Client/src/packages/core/components/entity-frame/entity-frame.element.ts | Implements <umb-entity-frame> rendering and styling (border + tab). |
| src/Umbraco.Web.UI.Client/src/packages/core/components/entity-frame/entity-frame.stories.ts | Adds Storybook stories demonstrating default/hover/focus/slot/custom-color/nested usage. |
| src/Umbraco.Web.UI.Client/src/packages/core/components/entity-frame/entity-frame.test.ts | Adds unit tests verifying basic render behavior and optional a11y audit. |
Comments suppressed due to low confidence (2)
src/Umbraco.Web.UI.Client/src/packages/core/components/entity-frame/entity-frame.element.ts:66
- The tab text color uses
var(--uui-color-surface, white), but--uui-color-surfaceis a background/surface token (e.g. dark theme sets it to a dark color). This makes the label low-contrast/incorrect in non-light themes. Use a contrast token (e.g.--uui-color-selected-contrast) or introduce a dedicated--umb-entity-frame-contrast-colorCSS custom property for the tab text.
background: var(--umb-entity-frame-color, var(--uui-color-focus));
color: var(--uui-color-surface, white);
padding: var(--uui-size-2) var(--uui-size-2) var(--uui-size-1);
src/Umbraco.Web.UI.Client/src/packages/core/components/entity-frame/entity-frame.element.ts:71
umb-entity-frameis described as a passive overlay, but.tabsetspointer-events: auto. This can cause the hover-based visibility pattern to flicker (moving the pointer from the parent onto the tab ends:hoveron the parent) and can also block interactions above the parent when opacity is 0. Consider keeping the tab non-interactive (pointer-events: none) or only enabling pointer events when the frame is actually meant to be interactive/visible.
padding: var(--uui-size-2) var(--uui-size-2) var(--uui-size-1);
border-radius: var(--uui-border-radius) var(--uui-border-radius) 0 0;
font-size: var(--uui-type-small-size);
line-height: 1;
pointer-events: auto;
}
- Remove `pointer-events: auto` from `.tab` so the overlay is truly passive (was intercepting events above the parent and causing hover flicker when toggled via opacity). - Replace `--uui-color-surface` tab text with `--uui-color-selected-contrast` (the proper paired contrast token) and expose `--umb-entity-frame-contrast-color` so consumers can override when supplying a non-default `--umb-entity-frame-color`. Fixes contrast in dark and high-contrast themes. - Add `aria-hidden="true"` to `.tab`; the frame is purely decorative and the parent owns the real semantics. - Add a unit test verifying slot content takes precedence over the `label` property.
|
Azure Static Web Apps: Your stage site is ready! Visit it here: https://orange-sea-0c7411a03-22844.westeurope.6.azurestaticapps.net |
Cherry picked from PR #22844
AndyButland
left a comment
There was a problem hiding this comment.
This looks good to me @leekelleher. I had Claude help me spin up a number of examples, and they all render as expected (checking colours, borders, long labels, hover states etc.):
Just had one accessibility related question that I noted inline.
As will need to be used with assistive technologies.
AndyButland
left a comment
There was a problem hiding this comment.
All looks good to me now then @leekelleher. I'll leave you to merge when you are ready, as I see you had a review pending from Niels (but he's away for a few days now, so up to you whether to wait).
|
Azure Static Web Apps: Your stage site is ready! Visit it here: https://orange-sea-0c7411a03-22844.westeurope.6.azurestaticapps.net |
* 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>
Description
As part of the Global Elements phase 2 work (Reusable Content of Blocks, see #22448, for Umbraco 19), we need UI to distinguish a referenced global element from a local block. Introducing an
<umb-entity-frame>component brings us a step closer towards this.Summary (AI/Claude generated) 🤖
umb-entity-framecomponent undersrc/packages/core/components/— a passive overlay that frames its parent with a rounded border and label tab.--umb-entity-frame-opacityCSS custom property (defaults to1); typical pattern is for the parent container to set it to0and toggle to1on:hoverand/or:focus-within.--umb-entity-frame-opacity,--umb-entity-frame-color, and--umb-entity-frame-border-width.Test Plan
Generic Components/Entity Frameand verify all 8 stories render correctlyOnHover,OnHoverOrFocus,WrappingButton,Nested) toggle visibility as expectedWithCustomColorstory renders with the purple theming (#7532c8)WithSlotstory shows the slotted icon + text instead of the label fallbacknpm test -- --files "**/entity-frame.test.ts"npx tsc --noEmit