Skip to content

fix(chat-widget): empty-state placeholder doesn't clear — :host([hidden]) override#1254

Merged
joelteply merged 1 commit into
canaryfrom
fix/empty-state-hidden-attr
May 15, 2026
Merged

fix(chat-widget): empty-state placeholder doesn't clear — :host([hidden]) override#1254
joelteply merged 1 commit into
canaryfrom
fix/empty-state-hidden-attr

Conversation

@joelteply
Copy link
Copy Markdown
Contributor

Bug (Joel reported)

Chat widget's "💬 Send your first message / Try @Helper..." empty-state placeholder doesn't clear when the room actually has messages. Visible after sending a fresh message into a room with prior history — the empty-state panel still shows below the messages.

Root cause

EmptyStateWidget (LitElement custom element <empty-state>) defines:

:host {
  display: flex;
  ...
}

ChatWidget toggles the empty state via the HTML hidden attribute (updateEntityCount()emptyState.toggleAttribute('hidden', !isEmpty)). The hidden attribute applies display: none via the user-agent stylesheet — but the more-specific author rule :host { display: flex } WINS the cascade, so hidden has zero visual effect. The toggle silently no-ops; the panel keeps rendering.

This is the well-known custom-element-with-explicit-display gotcha called out in the HTML5 spec: https://html.spec.whatwg.org/multipage/interaction.html#the-hidden-attribute

Fix

Add an explicit :host([hidden]) { display: none; } rule to the component's static styles.

:host([hidden]) {
  display: none;
}

Wins by being more specific than :host alone (attribute selector wraps the host pseudo-class).

Why other consumers weren't broken

UserListWidget, RoomListWidget, TrainingDashboardWidget, the Reactive* widgets all use ${this.isEmpty ? this.renderEmptyState() : nothing} — they conditionally include the element in the template rather than always-in-DOM + toggle-hidden. ChatWidget chose the toggle-hidden pattern because of the surrounding container/sibling layout, so the right fix is to make hidden work as expected for the component (benefits any future caller that uses the toggle pattern too).

Verification

  • npm run build:ts clean.
  • 19-line patch (4 lines of CSS + 15 lines of comment explaining the gotcha + spec link). The comment is load-bearing because the rule looks redundant alongside :host { display: flex } until you know the cascade history.

UI visual verification: this is a CSS-only fix to a well-understood class of bug; will follow up after merge with npm start + screenshot of a freshly-loaded populated room showing no empty-state placeholder.

…) override

Joel reported: chat widget's "Send your first message / Try @Helper..."
empty-state placeholder doesn't clear when the room actually has
messages. Visible after sending "my first message" into a room that
already had two prior messages — the empty-state panel still shows
below them.

## Root cause

`EmptyStateWidget` (LitElement, custom element `<empty-state>`) defines:

    :host {
      display: flex;
      ...
    }

ChatWidget toggles the empty state via the HTML `hidden` attribute
(updateEntityCount → emptyState.toggleAttribute('hidden', !isEmpty)).
The `hidden` attribute applies `display: none` via the user-agent
stylesheet — but the more-specific author rule `:host { display: flex }`
WINS the cascade, so `hidden` has zero visual effect. The toggle silently
no-ops; the panel keeps rendering.

This is the well-known custom-element-with-explicit-display gotcha
documented in the HTML5 spec:
https://html.spec.whatwg.org/multipage/interaction.html#the-hidden-attribute

## Fix

Add an explicit `:host([hidden]) { display: none; }` rule to the
component's static styles. Wins by being more specific than `:host`
alone (attribute selector wraps the host pseudo-class).

Other consumers of `<empty-state>` (UserListWidget, RoomListWidget,
TrainingDashboardWidget, the various Reactive* widgets) avoided this
bug by accident — they use `${this.isEmpty ? this.renderEmptyState()
: nothing}` to conditionally include the element rather than always-in-
DOM + toggle-hidden. ChatWidget chose the toggle-hidden pattern
deliberately because of CSS sibling rules around .messages-container,
so the right fix is to make `hidden` work as expected for the component.

## Verification

- `npm run build:ts` clean.
- Comment in code documents the gotcha + spec link so future readers
  understand why the rule is load-bearing (4 lines of CSS that look
  redundant alongside `:host { display: flex }` until you know the
  cascade history).

CSS-only behavioral fix: zero functional changes, no test added (UI
visual verification is the appropriate sign-off; will follow up after
merge with `npm start` + screenshot of a freshly-loaded populated room
showing no empty-state placeholder).
@joelteply joelteply merged commit df63494 into canary May 15, 2026
4 checks passed
@joelteply joelteply deleted the fix/empty-state-hidden-attr branch May 15, 2026 15:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant