Migrate dialogs to Web Awesome wa-dialog - #53
Conversation
Replace every custom <button> element and its per-component CSS with <wa-button> from the @home-assistant/webawesome fork, fulfilling the existing repo convention (contributing.md: prefer <wa-*> components). - Map variants/states: brand/danger + accent/outlined/plain, disabled, and loading (drive refresh uses the native loading prop) - Add ha-svg-icon (ported from the frontend) for inline MDI paths in button slots; icons live in start/end slots instead of custom markup - Extract the toolbox FAB into a fab-button component using wa-tooltip - Map Web Awesome color tokens onto our --ha-* theme colors in styles.css so buttons keep the Home Assistant palette in light and dark - Add vite-env.d.ts for ambient *.css / vite client types Removes ~450 lines of duplicated per-component button styles. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Retarget unit-test selectors from removed CSS classes (.back-button, .dialog-button.*, .lets-go-button, .footer-button.primary) to wa-button via stable container/attribute selectors; fixes 29 failing tests - Remove dead ?loading binding on the drive refresh buttons (they're never rendered while _loading is true — the view swaps to the spinner) - fab-button: make label optional so ifDefined() actually omits aria-label for icon-only use (empty-string default would have kept aria-label="") - Add unit tests for ha-svg-icon and fab-button - web-test-runner: prefer the "browser" export condition so nanoid (pulled in transitively via wa-tooltip) resolves its browser build instead of the node:crypto one, which failed to import under the test runner Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Migrate all Playwright E2E locators off the removed CSS classes
(.lets-go-button, .back-button, .cancel-button, .footer-button.primary,
.dialog-button.*, .download-button, .refresh-button) to wa-button selectors
- Replace toBeDisabled()/toBeEnabled() with toHaveJSProperty("disabled", …);
Playwright can't read a custom element's disabled state
- Wait for the proxmox configure Next button to enable before clicking (its
node/storage defaults load async; Playwright no longer auto-waits on
wa-button), fixing the proxmox flow specs
- drive-selection: show the full-page spinner only on the initial scan; a
refresh now keeps the list visible and drives the header button's native
loading prop, matching the PR description
- welcome CTA: size="large" -> size="l" (long form deprecated in the fork)
Full chromium E2E suite passes serially (174/174); unit 178/178.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Playwright's isEnabled()/isDisabled() only recognize native/ARIA disabled state, so on a wa-button host they always report enabled. The UTM flow used them to branch (skip/throw) and in one tautological assertion, so the checks were bypassed. Read wa-button's `disabled` JS property directly instead, via a small buttonDisabled() helper. UTM e2e spec passes (42/42 chromium). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Replace the hand-rolled overlay/panel dialogs with Web Awesome's <wa-dialog>, which brings a focus trap, Escape-to-close, backdrop dismiss, a header close button, and role="dialog"/aria-modal for free — resolving the dialog accessibility gaps from the frontend review (home-assistant#5). - Public APIs are unchanged: confirm-dialog keeps open/driveName + dialog-confirm/dialog-cancel; info-dialog keeps open/title/message/ primaryLabel/secondaryLabel + dialog-primary/dialog-secondary. Escape, backdrop, and the close button map to cancel/secondary (dismiss), with a guard so an action button doesn't double-fire a dismiss event. - Map --wa-color-surface-raised/--wa-color-text-normal/--wa-color-overlay-modal (and define --ha-card-background) so dialogs follow prefers-color-scheme; Web Awesome's own dark tokens are gated on a .wa-dark class we don't set. - Update unit tests for the wa-dialog structure and wa-hide dismiss; update the mini-PC E2E "Go back" locator to target appearance="outlined" so it doesn't also match wa-dialog's built-in close button. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #53 +/- ##
==========================================
- Coverage 89.54% 89.30% -0.24%
==========================================
Files 29 31 +2
Lines 10910 10731 -179
Branches 84 99 +15
==========================================
- Hits 9769 9583 -186
- Misses 1141 1148 +7
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
Pull request overview
Migrates custom dialogs to Web Awesome while incorporating the stacked button migration and associated accessibility, theming, and test updates.
Changes:
- Replaces confirmation and information overlays with
wa-dialog. - Migrates buttons to
wa-buttonand adds reusable icon/FAB components. - Updates dependencies, theme tokens, unit tests, and E2E selectors.
Reviewed changes
Copilot reviewed 31 out of 33 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
web-test-runner.config.mjs |
Enables browser dependency exports. |
test/unit/views/welcome-view.test.ts |
Updates button selectors. |
test/unit/views/path-selection-view.test.ts |
Updates back-button selectors. |
test/unit/components/wizard-shell.test.ts |
Updates wizard button tests. |
test/unit/components/info-dialog.test.ts |
Tests migrated information dialog. |
test/unit/components/ha-svg-icon.test.ts |
Adds SVG icon tests. |
test/unit/components/fab-button.test.ts |
Adds FAB component tests. |
test/unit/components/confirm-dialog.test.ts |
Tests migrated confirmation dialog. |
test/e2e/utm-flow.spec.ts |
Updates UTM flow selectors and state checks. |
test/e2e/proxmox-flow.spec.ts |
Updates Proxmox flow selectors and waits. |
test/e2e/other-options-flow.spec.ts |
Updates navigation selectors. |
test/e2e/navigation.spec.ts |
Updates general wizard selectors. |
test/e2e/minipc-flow.spec.ts |
Updates mini-PC dialog and button selectors. |
test/e2e/ha-hardware-flow.spec.ts |
Updates hardware-flow selectors. |
src/vite-env.d.ts |
Adds Vite client types. |
src/views/welcome-view.ts |
Migrates the primary CTA. |
src/views/utm/utm-check-view.ts |
Migrates UTM actions and icons. |
src/views/sbc/drive-selection-view.ts |
Migrates refresh/retry controls. |
src/views/sbc/device-selection-view.ts |
Migrates retry control. |
src/views/path-selection-view.ts |
Migrates back control. |
src/views/other-options-view.ts |
Migrates back control. |
src/views/minipc/architecture-selection-view.ts |
Migrates retry control. |
src/views/ha-hardware/device-selection-view.ts |
Migrates retry control. |
src/styles.css |
Maps application theme tokens to Web Awesome. |
src/main.ts |
Loads the Web Awesome theme. |
src/components/wizard-shell.ts |
Migrates wizard navigation controls. |
src/components/info-dialog.ts |
Reimplements information dialog with wa-dialog. |
src/components/ha-svg-icon.ts |
Adds reusable SVG icon component. |
src/components/fab-button.ts |
Adds Web Awesome-based floating action button. |
src/components/confirm-dialog.ts |
Reimplements confirmation dialog with wa-dialog. |
src/components/app-shell.ts |
Uses the new FAB component. |
package.json |
Switches to the Home Assistant Web Awesome fork. |
package-lock.json |
Locks the updated dependency graph. |
| </div> | ||
| </div> | ||
| </div> | ||
| <wa-dialog .open=${this.open} light-dismiss @wa-hide=${this._onWaHide}> |
| </div> | ||
| </div> | ||
| </div> | ||
| <wa-dialog .open=${this.open} light-dismiss @wa-hide=${this._onWaHide}> |
| id="button" | ||
| variant="brand" | ||
| appearance="accent" | ||
| aria-label=${ifDefined(this.label)} |
- Dialogs: sync state and dispatch the dismiss event on wa-after-hide instead of wa-hide. wa-hide is the cancelable *request* (before the 200ms close animation); feeding open=false back into wa-dialog there made its watcher start a second requestClose mid-animation. Use an explicit _closingViaAction flag (instead of reading `open`) so action-button closes don't also emit a dismiss event — the previous open-based guard was racy. - fab-button: a host aria-label isn't forwarded to wa-button's shadow-root <button>, so the icon-only control had no accessible name. Put the label in the button as visually-hidden text (keeping the tooltip); assert the name in the unit test. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Web Awesome's dark tokens are gated on a `wa-dark` class, not prefers-color-scheme, so unmapped tokens (neutral hover fill, tooltip colors) stayed light in dark mode. Toggle `wa-dark` on <html> from matchMedia so every Web Awesome component flips correctly, instead of mapping tokens one by one. Our --ha-* overrides still pin brand/danger/surface to Home Assistant colors. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
| // Fires after the dialog has fully closed (Escape / backdrop / header close | ||
| // button). Syncing here rather than on wa-hide avoids feeding open=false back | ||
| // into wa-dialog mid-animation, which would trigger a second close request. | ||
| private _onAfterHide() { |
| el.shadowRoot!.querySelector("wa-dialog")!.dispatchEvent( | ||
| new CustomEvent("wa-after-hide", { bubbles: true, composed: true }) | ||
| ); |
| el.shadowRoot!.querySelector("wa-dialog")!.dispatchEvent( | ||
| new CustomEvent("wa-after-hide", { bubbles: true, composed: true }) | ||
| ); |
wa-after-hide bubbles and is composed, so a nested Web Awesome overlay slotted into a dialog body would reach the wrapper's handler and wrongly dismiss the outer dialog. Guard on eventPhase === AT_TARGET (the same idiom the frontend's ha-dialog uses) so only the dialog's own close is handled. Add a bubbling test, and dispatch the AT_TARGET wa-after-hide the real close emits in the no-double-dismiss guard tests. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
| it("dispatches dialog-cancel when dismissed (escape/backdrop/close)", async () => { | ||
| const el = await fixture<ConfirmDialog>(html` | ||
| <confirm-dialog open></confirm-dialog> | ||
| `); | ||
|
|
||
| const overlay = el.shadowRoot!.querySelector(".overlay") as HTMLElement; | ||
| const dialog = el.shadowRoot!.querySelector("wa-dialog")!; | ||
|
|
||
| setTimeout(() => overlay.click()); | ||
| setTimeout(() => | ||
| dialog.dispatchEvent( | ||
| new CustomEvent("wa-after-hide", { bubbles: true, composed: true }) | ||
| ) | ||
| ); |
| it("dispatches dialog-secondary when dismissed (escape/backdrop/close)", async () => { | ||
| const el = await fixture<InfoDialog>(html` | ||
| <info-dialog open></info-dialog> | ||
| `); | ||
|
|
||
| const overlay = el.shadowRoot!.querySelector(".overlay") as HTMLElement; | ||
| const dialog = el.shadowRoot!.querySelector("wa-dialog")!; | ||
|
|
||
| setTimeout(() => overlay.click()); | ||
| setTimeout(() => | ||
| dialog.dispatchEvent( | ||
| new CustomEvent("wa-after-hide", { bubbles: true, composed: true }) | ||
| ) | ||
| ); |
The unit "dismissed" tests only exercise the local wa-after-hide handler, so a regression in the wa-dialog dismissal integration (Escape / backdrop / close button) would go unnoticed. Add Playwright tests that press Escape on the open confirm-dialog (SBC flow) and info-dialog (mini-PC flow) and assert the dialog closes and the underlying view remains. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
| if (this._closingViaAction) { | ||
| this._closingViaAction = false; | ||
| return; | ||
| } | ||
| this.open = false; | ||
| this._dispatch("dialog-cancel"); |
| if (this._closingViaAction) { | ||
| this._closingViaAction = false; | ||
| return; | ||
| } | ||
| this.open = false; | ||
| this._dispatch("dialog-secondary"); |
| el.shadowRoot!.querySelector("wa-dialog")!.dispatchEvent( | ||
| new CustomEvent("wa-after-hide", { bubbles: true, composed: true }) | ||
| ); |
| primaryButton.click(); | ||
| await el.updateComplete; | ||
| el.shadowRoot!.querySelector("wa-dialog")!.dispatchEvent( | ||
| new CustomEvent("wa-after-hide", { bubbles: true, composed: true }) | ||
| ); |
| /* wa-dialog surface/text/backdrop. Web Awesome's own dark tokens are gated | ||
| * on a .wa-dark class we don't set, so map these to our theme vars (which | ||
| * flip via prefers-color-scheme below) to keep dialogs correct in dark mode. */ |
There was a problem hiding this comment.
| /* wa-dialog surface/text/backdrop. Web Awesome's own dark tokens are gated | |
| * on a .wa-dark class we don't set, so map these to our theme vars (which | |
| * flip via prefers-color-scheme below) to keep dialogs correct in dark mode. */ | |
| /* wa-dialog surface/text/backdrop. Web Awesome's own dark tokens are gated | |
| * to WA's neutral palette, so map them to our theme vars instead. */ |
We do set .wa-dark now so this comment can be updated
Migrates the two hand-rolled dialogs (
confirm-dialog,info-dialog) to Web Awesome's<wa-dialog>, continuing the Web Awesome adoption from #5 / #16.Why
<wa-dialog>brings a focus trap, Escape-to-close, backdrop dismiss, a header close button, androle="dialog"/aria-modalfor free — resolving the dialog accessibility gaps called out in the review (no focus management, no Escape, no ARIA on the old custom overlays).What changed
confirm-dialogandinfo-dialognow wrap<wa-dialog>. Their public APIs are unchanged, soapp-shellandminipc-setup-method-viewneed no changes:open,driveName, eventsdialog-confirm/dialog-cancelopen,title,message,primaryLabel,secondaryLabel, eventsdialog-primary/dialog-secondarydialog-cancel/dialog-secondary), with a guard so an action button doesn't also fire a dismiss event..wa-darkclass this app doesn't set, so the dialog surface would otherwise stay light. Mapped--wa-color-surface-raised/--wa-color-text-normal/--wa-color-overlay-modalto our--ha-*vars (and defined--ha-card-background) so dialogs followprefers-color-scheme.wa-dialog+wa-hidedismiss) and one mini-PC E2E locator (Go backnow targetsappearance="outlined"so it doesn't also match wa-dialog's built-in close button).Testing
tsc,eslint,prettier --check,vite buildall pass.🤖 Generated with Claude Code