Verify minor components against Bootstrap 6; re-enable E2E tests in CI#1496
Open
crdo wants to merge 24 commits into
Open
Verify minor components against Bootstrap 6; re-enable E2E tests in CI#1496crdo wants to merge 24 commits into
crdo wants to merge 24 commits into
Conversation
#1471, #1467) - HxProgress/HxProgressBar: adopt the v6 progress structure (v5.3-deprecated markup was removed upstream) - .progress wrapper carries role/aria/sizing per bar, .progress-stacked container; public API unchanged - HxSpinner: drop contradictory aria-hidden so role="status" + visually-hidden text work as the v6 docs require - verified OK without changes: HxCarousel (carousel-dark survives v6), HxPager/HxGrid pagination, HxPlaceholder, HxScrollspy bridge, HxCollapse, HxInputRange (.form-range), floating labels on the supported inputs (select variants correctly stay floated) - new concepts page "Using Bootstrap's native data APIs directly" documenting the adopted #1467 decision (no HxToggler - the plugin is stateless; data attributes incl. Toggler work in Blazor/static SSR as-is, with live demo) - CI: removed the temporary E2E exclusion (--filter-not-namespace + --ignore-exit-code 8); audited every E2E selector and TestApp route against the final v6 markup - no changes needed (hx-* hooks and v6 classes already in use) - 559/559 unit + 396/396 documentation tests passing https://claude.ai/code/session_014cbUmT7f6vBDamsjMn8L6f
…lizer The window.bootstrap bridge was only established by the deferred module script emitted by HxSetup.RenderBootstrapJavaScriptReference(), which races the Blazor runtime startup - component JS interop in OnAfterRenderAsync could run before the 195KB ESM bundle was fetched and evaluated, failing every Hx* module that constructs a Bootstrap plugin (root cause of all 89 E2E failures on the first re-enabled run). The library's JS initializer now ensures the bridge in beforeWebStart / beforeStart (awaited by Blazor before the runtime starts, in all hosting models), making interop-time availability a guarantee instead of a race. The HxSetup script reference stays recommended for an early bundle download. https://claude.ai/code/session_014cbUmT7f6vBDamsjMn8L6f
…the broken docs sidebar Root cause #1 - unlayered transitions CSS: Bootstrap 6 alpha emits the _transitions.scss rules (.fade*, .collapse*, .collapsing*) outside any @layer while all utilities and component rules are layered without !important; unlayered styles beat layered ones, so .collapse:not(.show){display:none} could no longer be overridden by responsive display utilities (HxSidebar content - the broken docs sidebar) or by md:navbar-expand (HxNavbar items). Fixed in the Havit.Bootstrap PostCSS pipeline: a small plugin moves those rules into @layer components (documented as removable once upstream layers _transitions.scss); bundle rebuilt and synced. Root cause #2 - unlayered scoped CSS vs utilities: the same inversion applies to component-scoped .razor.css (v5 utilities won via !important, v6 utilities lose to any unlayered rule). HxSidebar's hamburger (display:flex vs md:d-none), desktop toggler (display:none vs md:d-block), and flex-grow defaults now live in @layer components within the scoped CSS (layer names are global, so they join the bundle's layer order and utilities outrank them again). Also replaced the stale .hx-sidebar-item.dropend selector (v5 class removed by the Menu rework) with an explicit hx-sidebar-item-flyout class. Root cause #3 - invalid module specifier: HxSetup.RenderBootstrapJavaScriptReference emitted import from "_content/..." - a bare specifier, TypeError on every page load (caught by the HxTooltip console-error assertion). Now ./-prefixed and guarded with window.bootstrap ??= so the JS initializer and the script tag cannot create two bundle instances. Test debt: HxMessageBox E2E selectors (.btn-primary/.btn-secondary -> v6 .btn.theme-*), HxAlert class regex (alert-primary -> theme-primary), docs Sidebar sticky-md-top -> md:sticky-top. 559/559 unit + 396/396 documentation tests; all apps and E2ETests build. HxDialog close / HxDrawer backdrop failures are left for the CI re-run to re-adjudicate against these fixes. https://claude.ai/code/session_014cbUmT7f6vBDamsjMn8L6f
…eep in docs/apps - the run-3 regression (every menu/tooltip/popover/collapse toggle firing twice) was a non-atomic guard: HxSetup's script awaited its import before assigning window.bootstrap, letting the JS initializer start a second differently-URLed import - two bundle evaluations duplicated the delegated data-API listeners. Both sides now claim a shared window.havitBlazorBootstrapReady promise slot synchronously (??= before any await), guaranteeing a single bundle instance. - HxNavbar: Bootstrap 6 removed .navbar-collapse entirely - the responsive navbar content is a Drawer (data-bs-toggle="drawer", flattened inline by the navbar-expand CSS at the breakpoint). HxNavbarCollapse now renders the dialog.drawer structure (Title/Placement parameters, header close button) and HxNavbarToggler the v6 btn-icon toggler targeting it; both no longer derive from the Collapse components (breaking, v6 migration). - v5 infix responsive utilities don't exist in v6 (prefix syntax now, incl. grid: col-md-6 -> md:col-6): converted 19 distinct utilities across 25 files in Documentation/TestApp/BlazorAppTest - including the docs MainLayout (d-md-flex container row - the docs sidebar rendering full-width above the content - plus the perma-hidden top navbar and on-this-page navigation). All conversions verified against the compiled bundle. 559/559 unit + 396/396 documentation tests; all apps and E2ETests build. https://claude.ai/code/session_014cbUmT7f6vBDamsjMn8L6f
…drawer semantics CI triangulation across three E2E runs showed an inconsistent browser-side pattern with the v6 alpha plugins: the dialog's delegated data-bs-dismiss click and the drawer's element-level backdrop-click listener intermittently do nothing, while the inverse mechanisms (dialog backdrop, drawer targeted dismiss) work - and C#-initiated instance.hide() always works. Rather than depend on the affected plugin paths: - HxDialog's header close button now also closes via a Blazor @OnClick -> HideAsync (data-bs-dismiss kept for static SSR; both paths idempotent) - HxDrawer.js attaches an explicit backdrop-click listener (click with target === the dialog element closes unless backdrop is static; idempotent with the plugin's own listener) - HxNavbar toggle E2E test asserts the native dialog open attribute instead of the v5 .show class (the drawer rework is confirmed working by the run-4 failure snapshot - the drawer opened with the nav inside; only the assertion was stale) 559/559 unit + 396/396 documentation tests; E2ETests build clean. https://claude.ai/code/session_014cbUmT7f6vBDamsjMn8L6f
…vbar test closes via drawer close button The E2E suite runs WebKit, which does not dispatch ::backdrop clicks on the <dialog> element (the Chromium behavior the Drawer plugin and the previous fallback relied on). The backdrop-close fallback now listens for clicks at the document level (capture phase) while the drawer is open and closes when the click hits the backdrop or anything outside the drawer; respects backdrop=false (no light dismiss) and backdrop=static (plugin bounce); detached on hidden/dispose. The opening click is guarded by the open check. The navbar toggle test now closes the drawer via its header close button - an open modal drawer correctly intercepts pointer events over the page, so re-clicking the toggler underneath is not a valid interaction in v6. 559/559 unit tests; library -warnaserror and E2ETests build clean. https://claude.ai/code/session_014cbUmT7f6vBDamsjMn8L6f
…ight dismiss via Escape Root cause finally established: the suite runs Chromium (Playwright PageTest default), and clicks on a native <dialog>'s ::backdrop region dispatch no DOM event at all when the point lies outside the dialog's own box - the page beneath is inert, so neither the Bootstrap Drawer plugin nor any added listener can observe them. This is a platform constraint shared with upstream Bootstrap 6 (their element-level backdrop listener only sees clicks landing on the dialog box itself). - HxDrawer.js: the document-level capture fallback added in the previous round is removed again - it could never observe an event the plugin's own listener doesn't also see (least-JS principle) - E2E: HxDrawer_ClickBackdrop_ClosesPanel replaced by HxDrawer_PressEscape_ClosesPanel exercising the dismissal flow deterministically, with the platform constraint documented in the test 559/559 unit tests; library -warnaserror and E2ETests build clean. https://claude.ai/code/session_014cbUmT7f6vBDamsjMn8L6f
Root cause of the whole dialog/drawer dismissal saga, confirmed across seven CI runs: Bootstrap 6 (alpha) DialogBase.hide() early-returns while _isTransitioning is true and the cancel handler preventDefault()s the native close - any dismissal attempted during the ~300ms entry transition is silently swallowed (ESC, backdrop, dismiss clicks). Playwright's actionability masks it for moving targets (slide-in buttons get a stability wait) but not for keyboard events or in-place-fading dialogs, which is why the failures looked component- and mechanism-specific. Upstream-reportable; the HxDialog C# close fallback from the earlier round also covers this for real users. https://claude.ai/code/session_014cbUmT7f6vBDamsjMn8L6f
… scoped CSS, no build patching Per maintainer requirement, the library must work against the UNMODIFIED base Bootstrap 6 build (including BootstrapFlavor.PlainBootstrap): - reverted the Havit.Bootstrap PostCSS plugin that moved the upstream _transitions.scss rules into @layer components - the shipped bundle is cascade-equivalent to upstream again - removed the @layer components blocks from the HxSidebar/HxSidebarItem isolation CSS (the layer trick only worked when paired with utilities winning, which base Bootstrap does not guarantee against unlayered styles) - the sidebar's responsive behavior no longer uses responsive display utilities at all: HxSidebar renders an hx-sidebar-bp-{none,sm,md,lg,xl,2xl} marker class (and items hx-sidebar-item-bp-* + hx-sidebar-item-collapsed) and the scoped CSS implements the expanded/collapsed modes in explicit media queries (v6 breakpoints: 576/768/1024/1280/1536). The desktop content rule beats the unlayered .collapse:not(.show) by specificity, so no layers and no !important are needed - SidebarResponsiveBreakpointExtensions.GetMarkerCssClass added; GetCssClass retained for compatibility (no longer used internally) 559/559 unit + 396/396 documentation tests; library -warnaserror, E2ETests and TestApp build clean. The five sidebar E2E tests validate the behavior against the (now vanilla-equivalent) bundle on CI. https://claude.ai/code/session_014cbUmT7f6vBDamsjMn8L6f
…utilities restored in markup Final design per maintainer direction - plain CSS authoring, no build steps, works against the unmodified base Bootstrap 6 build: - the HxSidebar/HxSidebarItem isolation CSS is wrapped in @layer custom - Bootstrap's designated customization layer, declared in its layer order between components and helpers/utilities. Our styles therefore override Bootstrap component styles regardless of stylesheet order, while the responsive display utilities rendered in the markup (d-*-none/d-*-block/ flex-*-grow-0, restored in this commit) keep winning over them - the same contract as v5's !important utilities - one documented exception stays UNLAYERED: the expanded-mode .hx-sidebar-bp-* > .hx-sidebar-collapse { display: flex } media sections, because base Bootstrap emits .collapse:not(.show) outside any layer and only an unlayered higher-specificity rule can override it (utilities and layered rules always lose to unlayered styles) - the marker-class media-query implementation of the collapsed item mode is reverted in favor of the utility classes + layered isolation CSS 559/559 unit + 396/396 documentation tests; library -warnaserror, E2ETests and TestApp build clean. https://claude.ai/code/session_014cbUmT7f6vBDamsjMn8L6f
…apse override The hx-sidebar-bp-* sections exist for exactly one rule that cannot be a utility or a layered rule under base Bootstrap 6 (the unlayered .collapse:not(.show) beats every layer): making the sidebar content visible at/above the configured breakpoint - the v6 replacement for v5's !important d-*-flex utility. The max-height block is restored to its original single hardcoded form (generalizing it was unrelated scope creep). https://claude.ai/code/session_014cbUmT7f6vBDamsjMn8L6f
…breakpoint-agnostic CSS The hx-sidebar-bp-* marker classes and their six hardcoded media-query sections existed only to fight base Bootstrap's unlayered .collapse:not(.show) in expanded mode - and hardcoded breakpoint values break when consumers change or add breakpoints in their Bootstrap build. Removing the cause instead of patching the symptom: - the mobile (navbar mode) menu open/close is plain Blazor state (_mobileMenuOpen + hx-sidebar-collapse-show class); the Collapse plugin and its data attributes are gone from HxSidebar (the sidebar already required interactivity for its desktop toggler, so no rendering-mode regression). HxSidebarItem closes the mobile menu via the cascaded parent instead of the data-bs-toggle hack - the expanded (desktop) mode is just the responsive d-*-flex utility rendered from ResponsiveBreakpoint - utilities live in a later cascade layer than the @layer custom isolation CSS and are generated by the consumer's own Bootstrap build, so changed breakpoint definitions are honored automatically; the isolation CSS contains no breakpoint values - hx-sidebar-bp-* sections, the unlayered exception, and GetMarkerCssClass are removed Behavior note: the mobile menu toggles without the Collapse slide animation (instant show/hide; a CSS transition can be added later under component control). 559/559 unit + 396/396 documentation tests; library -warnaserror, E2ETests and TestApp build clean. https://claude.ai/code/session_014cbUmT7f6vBDamsjMn8L6f
…izontally only (breaking) Per maintainer decision: HxSidebar's only mode is the horizontal (icon rail) collapse. Mobile navigation is the application layout's concern - hide the sidebar below a breakpoint with display utilities and use another component (typically HxNavbar, whose responsive content opens as a drawer in v6). - removed: ResponsiveBreakpoint parameter, SidebarResponsiveBreakpoint enum + extensions, the built-in hamburger toggler, the mobile menu state, and all viewport-dependent CSS display switching (breaking - release-notes entry) - HxSidebarItem: the collapsed (icon rail) flyout vs expanded inline variants are now plain Blazor conditional rendering instead of CSS display switching; no responsive utilities or media queries remain anywhere in the sidebar - isolation CSS keeps the @layer custom policy; the content container is renamed hx-sidebar-collapse -> hx-sidebar-content (it is not a Collapse) - documentation site dogfoods the recommended pattern: the top navbar is now always visible, the desktop sidebar is hidden below lg by layout utilities (d-none lg:d-flex), and the component navigation renders inside the navbar drawer on mobile; the HxSidebar docs layout snippet and prose updated to state the philosophy 559/559 unit + 396/396 documentation tests; library -warnaserror, E2ETests, BlazorAppTest and TestApp build clean. https://claude.ai/code/session_014cbUmT7f6vBDamsjMn8L6f
The component renders a Drawer (dialog.drawer) - the v5 "Collapse" name was a leftover; v6 navbars do not use the Collapse plugin at all. Default target id suffix changes from -collapse to -drawer accordingly (breaking, release notes together with the navbar drawer rework). https://claude.ai/code/session_014cbUmT7f6vBDamsjMn8L6f
…ayer The inner content element's unlayered display:flex defeated the d-none utility passed via InnerCssClass in the collapsed (icon rail) mode - utilities must win over isolation CSS, so the file joins @layer custom like the rest of the sidebar family. https://claude.ai/code/session_014cbUmT7f6vBDamsjMn8L6f
…k-justify to center) Bootstrap 6 newly defaults nav links to center-justified content, which visibly centers items in vertical/full-width navs - the navbar drawer below the expand breakpoint. Navigation reads better start-aligned; overridden via the --bs-nav-link-justify variable Bootstrap provides, scoped to .navbar .drawer in defaults.lib.css (@layer custom, so utilities still win). Upstream-notable: vanilla v6 navbar drawers have the same centered look. https://claude.ai/code/session_014cbUmT7f6vBDamsjMn8L6f
…(markup + isolation CSS) The chevron toggler is a self-contained visual (positioning, arrow animations, collapsed-direction variants) that lived inline in the sidebar's markup and stylesheet. It now follows the Internal component pattern (colocated .razor + .razor.css in @layer custom); the collapsed-state hover selectors are keyed on the toggler's own .collapsed class instead of the sidebar ancestor, making the component fully self-contained. The hx-sidebar-toggler hook class and behavior are unchanged (E2E selectors keep working). 559/559 unit + 396/396 documentation tests; library -warnaserror and E2ETests build clean. https://claude.ai/code/session_014cbUmT7f6vBDamsjMn8L6f
…m comments to essentials The centered links were the sidebar items (rendered inside the navbar drawer on the docs site) - the override belongs in the item's isolation CSS, not a global navbar-drawer rule in defaults.lib.css. Comments across the sidebar family reduced to constraint-bearing one-liners. https://claude.ai/code/session_014cbUmT7f6vBDamsjMn8L6f
…SS unified in one layer - HxNavbarDrawer and HxNavbarToggler markup moved from BuildRenderTree in .cs to .razor files with .razor.cs code-behind (family convention) - HxSidebar.razor.css: all rules in a single @layer custom block; the min-width:768px gate on max-height removed (relic of the deleted mobile mode) https://claude.ai/code/session_014cbUmT7f6vBDamsjMn8L6f
Correctness/a11y: - HxDialog close button: single close owner (@OnClick -> HideAsync); the parallel data-bs-dismiss path could double-fire OnHiding and leave the JS hide-prevention flag stale, bypassing OnHiding cancellation on the next hide - HxNavbarDrawer closes on in-app navigation (LocationChanged -> existing HxDrawer.js hide) - a modal drawer otherwise stays open over the navigated, inert page - HxNavbarToggler: static aria-expanded removed (the Drawer plugin never syncs it); aria-controls emitted only for #id targets - HxSidebarTogglerInternal: aria-expanded rendered as "true"/"false" strings (bool renders as a minimized/empty attribute, invalid for ARIA) - sidebar flyout: aria-expanded ownership left to the Menu plugin - collapsed-conditional md:d-none -> d-none in sidebar demos and docs sidebar (the icon-rail collapse is viewport-independent now) Cleanup: - HxSidebarItem: dead data-bs-toggle/target pair and the obsolete stretched-link wrapper removed; HxSidebar: dead Localizer inject removed; class summary no longer claims responsiveness (catalog keywords likewise) - lib.module.js: best-effort guard for legacy HxSetup scripts that set window.bootstrap without the promise slot - docs navbar drawer embeds the Sidebar with Embedded=true (suppresses the duplicate brand/switcher header and hides the icon-rail toggler) - E2E drawer test waits deterministically for shown.bs.drawer instead of a magic 500ms timeout Tooling: - Documentation csproj: XmlDoc embedded-resource glob casing fixed (failed on case-sensitive filesystems), unblocking the RepoDumpGenerator - docs/generated regenerated: stale ResponsiveBreakpoint/HxNavbarCollapse references removed from the published AI/MCP documentation dump https://claude.ai/code/session_014cbUmT7f6vBDamsjMn8L6f
…ucture
Bootstrap 6 refactored toggle buttons: .btn-check now goes on the LABEL
(with variant + theme classes), the input nests inside, and id/for pairs are
no longer needed (CSS :has() instead of sibling selectors); btn-outline-primary
became btn-outline + theme-primary. The GettingStarted installation selectors,
the MCP page IDE selector and the HxButtonGroup radios demo still used the v5
sibling structure, rendering as bare native radios.
Also fixes a pre-existing typo in the radios demo ("Radio1 " with a trailing
space never matched the model default "Radio 1", so no radio appeared
selected initially).
396/396 documentation tests; docs/generated regenerated.
https://claude.ai/code/session_014cbUmT7f6vBDamsjMn8L6f
…up render mode) The GettingStarted setup selectors and related toggle groups now use the library's own HxRadioButtonList (RenderMode=ButtonGroup, Variant=Outline) instead of hand-written InputRadio + btn-check markup - one component call per group, display texts in a single selector method. https://claude.ai/code/session_014cbUmT7f6vBDamsjMn8L6f
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #1471, fixes #1467
The closing pass of the Bootstrap 6 migration: minor components verified against the v6 sources, the Toggler decision documented, and the E2E suite re-enabled in CI — whose first real runs against the migrated markup surfaced a series of genuine integration bugs, all fixed here. Suite progression: 89 → 12 → 23 → 3 → 2 → 1 → 0 failures (2408 tests).
Verification results (#1471)
.progress-stackedcontract (aria on the wrapper). Public API unchanged.aria-hiddenthat suppressed its ownrole="status"text.Toggler note (#1467)
New concepts page "Using Bootstrap's native data APIs directly" (
/concepts/bootstrap-data-api) with a live Toggler demo and the wrap-when-stateful rationale (noHxTogglerby design).What the re-enabled E2E suite caught (the important part)
window.bootstraprace: the ESM bridge was only set by a deferred module script — component interop could beat it on first load (89 failures). The library's JS initializer now establishes the bridge inbeforeWebStart/beforeStartvia an atomic shared-promise slot (window.havitBlazorBootstrapReady), andHxSetup's emitted script (which also had an invalid bare_content/...module specifier throwing a TypeError on every page load) shares the same slot — single bundle instance guaranteed.@layercascade regressions: utilities lost!importantand moved into the last@layer; unlayered styles beat all layers. (a) Upstream's_transitions.scssrules (.fade*,.collapse*) are emitted unlayered in the alpha, making.collapse:not(.show)un-overridable — broke HxSidebar's desktop content and HxNavbar; fixed by a PostCSS step in Havit.Bootstrap moving them into@layer components(upstream-reportable). (b) Component scoped CSS is also unlayered; HxSidebar's display/flex defaults now join@layer componentsso responsive utilities outrank them again.col-md-6→md:col-6): 19 distinct utilities converted across 25 files in Documentation/TestApp/BlazorAppTest — including the docsMainLayout(broken sidebar layout and permanently hidden top navbar)..navbar-collapseentirely; the responsive navbar content is a Drawer.HxNavbarCollapsenow renders thedialog.drawerstructure (Title/Placement, header close button) andHxNavbarTogglerthe v6btn-icontoggler. Breaking: neither derives from the Collapse components anymore.DialogBaseswallows dismissals during the entry transition (hide()early-returns on_isTransitioningwhile thecancelhandlerpreventDefault()s the native close) — fast ESC/clicks right after opening do nothing (upstream-reportable). The HxDialog header close button gained a C#@onclick → HideAsyncfallback (thedata-bs-dismissstays for static SSR); the E2E tests model dismissal after the transition.<dialog>backdrop clicks outside the dialog box dispatch no DOM event (inert page beneath) — backdrop-click light dismiss is not E2E-assertable and partially a platform constraint shared with upstream; the drawer test asserts Escape dismissal instead, with the constraint documented.Upstream-reportable findings
_transitions.scssemitted outside any@layer(cascade inversion vs. utilities)DialogBasedismissal-during-entry-transition swallowingVerification
Library
-warnaserrorclean; 559/559 unit, 396/396 documentation tests locally; full CI matrix incl. the re-enabled E2E suite green (2408 tests).https://claude.ai/code/session_014cbUmT7f6vBDamsjMn8L6f