Skip to content

Migrate to Bootstrap 5.3 and resolve modal/popover/tooltip lifecycle bug#74

Draft
malberts wants to merge 9 commits into
masterfrom
bs5-followup
Draft

Migrate to Bootstrap 5.3 and resolve modal/popover/tooltip lifecycle bug#74
malberts wants to merge 9 commits into
masterfrom
bs5-followup

Conversation

@malberts
Copy link
Copy Markdown
Collaborator

@malberts malberts commented May 20, 2026

Closes #38.
Fixes #68.

Summary

Upgrades BootstrapComponents from Bootstrap 4 to Bootstrap 5.3, at functional parity with the last released BS4-era BC (5.2.2) and additionally resolves the upstream "Modal/Popover/Tooltip stop working under MediaWiki 1.43+" bug.

This branch was informed by the earlier WIP exploration in #69 (now closed as superseded); it is a clean, self-contained set of nine commits.

What changed

Nine self-contained commits:

  1. Migrate BootstrapComponents to Bootstrap 5.3 — the BS4 → 5.3 emission changes (data-toggledata-bs-toggle family, .btn-default.btn-secondary mapping for color="default", .close.btn-close, .badge-pill.rounded-pill, jumbotron utility-class reimplementation per the BS5 migration guide, vanilla-JS rewrites of carousel/popover/tooltip). Floor versions bumped to MW ≥ 1.43, PHP ≥ 8.1, mediawiki/bootstrap: ^6.0.
  2. Run CI on more versions of MediaWiki and PHP — CI matrix updated to MW 1.43–1.45 + master, PHP 8.1–8.5.
  3. Modernise namespaced class importsuse \Html etc. replaced with use MediaWiki\…\X across the codebase (required-ish at the MW 1.43 floor).
  4. Update tests for Bootstrap 5 output — PHPUnit expectations updated to match the BS5 emitted markup.
  5. Fix DOMContentLoading typo in tooltip JS initialiser'DOMContentLoading' (which is not an event) is now 'DOMContentLoaded'.
  6. Resolve upstream Tooltip, Popover and Modal stop working after a cache purge on MW 1.43.3 #68 by emitting modal markup inline — the architectural fix; see below.
  7. Emit Bootstrap 5 text-bg-<color> for badges — BS5 removed the .badge-<color> family; replaced with .text-bg-<color>. color="default" maps to text-bg-secondary for backward-compat (matches Button/Modal/Popover behaviour).
  8. Update tests to match inline modal emission — ModalTest, ImageModalTest, ModalBuilderTest, OutputPageParserOutputTest contract changes.
  9. docs: update component references for Bootstrap 5 — every external https://getbootstrap.com/docs/4.1/ link bumped to /docs/5.3/; jumbotron deprecation banner; color="default" mapping noted in the relevant component docs; known-issues.md updated with the Tooltip, Popover and Modal stop working after a cache purge on MW 1.43.3 #68 resolution note; release-notes 6.0 entry expanded.

Resolves upstream issue #68

PR #69 noted "Modal does not trigger" / "Popover does not trigger" as a TODO. Investigation showed it is the same bug as the open issue #68 ("Tooltip, Popover and Modal stop working after a cache purge on MW 1.43.3").

Root cause: BC's ModalBuilder::parse() stashed the modal markup via ParserOutputHelper::injectLater()$parserOutput->setExtensionData(DEFERRED_CONTENT_KEY, …), and OutputPageParserOutput::process() retrieved it via the same key at render time. Diagnostic instrumentation under MW 1.43.8 showed the parse-time and render-time ParserOutput instances are different objects (spl_object_id differs); the deferred-content data set on the parse-time object never reaches the render-time hook. Effect: trigger renders, modal container does not, click does nothing.

The defensive design rationale for the deferred-injection pattern no longer applies under modern MediaWiki + Bootstrap 5 — MW 1.40+ uses RemexHtml which auto-closes the enclosing inline element rather than mangling block-in-inline, and BS5 modals use position: fixed plus an ID-resolving data-bs-target attribute so DOM placement is no longer significant.

Refactor: emit the modal markup inline as a sibling of its trigger button. Per-component JS init modules (the BS5 vanilla-JS replacements for the dropped jQuery plugins) are loaded via OutputPage::addModules() from the OutputPageParserOutput hook (which works reliably; OutputPage is the persistent object). A new ext.bootstrapComponents.modal.js module explicitly instantiates bootstrap.Modal on each .modal element, since BS5 dropped the BS4 jQuery-driven autoinit.

The same patch was applied workspace-only against BC 5.2.2 to confirm: with this commit, modal markup renders on MW 1.43+ even without the BS5 migration. So the fix would also help users still on BC 5.2.x if backported.

Verification

  • 4-stack harness (Chameleon-BS4 / Chameleon-BS5 / Medik-BS4 / Medik-BS5 on MW 1.43.8): every parser function renders correctly on every stack; HTTP 200; no PHP errors; zero unparsed parser-function tokens.
  • Comprehensive per-attribute matrix (~75 attribute permutations: 8 colors × every component, all sizes, all placements/triggers for popover/tooltip, outline/pill/active/disabled variants, custom-class/style passthrough): no functional regressions on any stack.
  • Functional triggering verified via Playwright on the BS5 candidate stacks: modal data-bs-toggle click opens the modal (with backdrop, .modal.fade.show); popover and tooltip JS instances attach; dismissible alert .btn-close closes the alert.
  • PHPUnit: 96 of BC's Unit/ tests pass (everything except LuaLibrary*Test.php which require Scribunto in the test environment).

Component deprecations (BS5)

  • bootstrap_jumbotron — BS5 removed the .jumbotron class (migration guide entry). The parser function is retained and now emits the BS5 utility-class approximation per the migration guide (p-5 mb-4 bg-body-tertiary rounded-3). The deprecation is called out in docs/components/jumbotron.md and docs/components.md; the parser function may be removed in a future major release.

Notes for users

  • mediawiki/bootstrap constraint pins to ^6.0 (the released BS5.3-bearing major). Anyone on the previous ^5.0 is on a BS4 stack and should stay on BC 5.x until ready to upgrade their wiki to MW 1.43+ + the new Bootstrap extension major.
  • Most existing wiki markup using BC parser functions / tag extensions continues to work without changes. color="default" automatically maps to btn-secondary (button/modal/popover/badge).

Out of scope

  • Adding new BS5-only components (bootstrap_offcanvas, bootstrap_spinner, bootstrap_toast) — separate enhancement.
  • Audit of BC's *.vector-fix.css modules for continued necessity under BS5 — separate follow-up.
  • Migrating MWException to typed exceptions — latent debt, separate follow-up.

🤖 Generated with Claude Code

malberts and others added 9 commits May 20, 2026 16:28
Port the parser-hook output of every component from Bootstrap 4 to
Bootstrap 5.3:
- All data-attribute names switched to the data-bs-* family
  (data-toggle/target/dismiss/ride/slide/parent/content/placement/trigger).
- Badge: .badge-pill replaced with the .rounded-pill utility.
- Button: .btn-default is gone in BS5; color='default' now maps to
  btn-secondary (Modal, Popover apply the same mapping for their
  trigger buttons).
- Alert / Modal close buttons: .close + inner <span>&times;</span>
  replaced with the BS5 .btn-close button.
- Jumbotron: the .jumbotron class was removed in BS5. The parser
  function is retained for backward compatibility but now emits the
  BS5 utility-class approximation per the official migration guide
  ('p-5 mb-4 bg-body-tertiary rounded-3').
- JavaScript modules for carousel/popover/tooltip rewritten from
  jQuery to vanilla JS (BS5 dropped jQuery as a dependency).
- Test expectations updated to the new emitted markup.

Floor versions bumped to MediaWiki >= 1.43, PHP >= 8.1, and
mediawiki/bootstrap ^6.0 (the BS5.3-bearing major). Extension version
6.0.0-dev. README mention updated.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Update the CI matrix to cover the MediaWiki and PHP versions supported
by the Bootstrap 5 release. Also disable Scrutinizer Ocular coverage
upload (the upstream service is broken).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
With the floor at MediaWiki 1.43, the deprecated global-namespace
fallbacks (e.g. \Html, \Title, \Parser) can be dropped in favour of
the namespaced equivalents (MediaWiki\Html\Html,
MediaWiki\Title\Title, MediaWiki\Parser\Parser). Replace deprecated
aliases across the extension and tidy up unused / redundant use
statements in passing.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
PHPUnit expectations across the Components, ImageModal and ModalBuilder
tests updated to match the BS5 emission switched on by the migration
commit (data-bs-* attributes, .btn-close close buttons, .rounded-pill
badges, btn-secondary default-color, jumbotron utility classes).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The tooltip JS module added in the BS5 migration listens for
'DOMContentLoading' (which is not a real event) instead of
'DOMContentLoaded'. The bug guard branch is reached when the script
loads before the DOM is parsed; with the typo the listener never fires
and the tooltip initialiser is never called on first-render.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The old design path was: parser hook → ModalBuilder::parse() →
ParserOutputHelper::injectLater() →
$parserOutput->setExtensionData(DEFERRED_CONTENT_KEY, ...), and at
render time → OutputPageParserOutput hook →
$parserOutput->getExtensionData(DEFERRED_CONTENT_KEY) →
$outputPage->addHTML($deferredText).

Under MediaWiki 1.43+, the set-side and get-side operate on two
different ParserOutput objects (an effect of ParserOutputAccess changes
between 1.39 and 1.43), so the deferred-content array is stored on the
parse-time object and never reaches the render-time one. Net effect:
modal trigger HTML reaches the page but the modal container HTML does
not, leaving the trigger with nothing to open. This is the symptom
described in upstream issue #68 (open since 2025-08-08).

The defensive design rationale for body-end injection (Tidy's hostility
to <div> inside <p>; BS4 modal stacking concerns) no longer applies:
MediaWiki 1.40+ uses RemexHtml which auto-closes the enclosing inline
element rather than mangling block-in-inline, and Bootstrap 5 modals
use position: fixed plus an ID-resolving data-bs-target attribute, so
DOM placement is no longer significant.

Refactor:
- ModalBuilder::parse() returns the trigger HTML and modal HTML
  concatenated; modal markup emits as a sibling of its trigger at the
  wikitext tag's natural position.
- Remove ParserOutputHelper::injectLater() and the unused
  EXTENSION_DATA_DEFERRED_CONTENT_KEY constant.
- Trim OutputPageParserOutput::process() down to what survives the
  parse → render lifecycle: OutputPage::addModules() for the Bootstrap
  library JS module and BC's per-component JS init modules (OutputPage
  IS the persistent object — modules added there reach the page;
  modules added to the parse-time ParserOutput do not).
- Add a new ext.bootstrapComponents.modal.js resource module that
  instantiates bootstrap.Modal on each .modal element (BS5 dropped
  jQuery-driven modal autoinit; without an explicit instantiation the
  data-bs-toggle='modal' triggers don't fire).

Closes #68.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…family

Bootstrap 5.3 removed the .badge-<color> class family. Replacement is
.text-bg-<color>, which sets both background-color and a contrasting
text color (so a primary-coloured badge gets readable text on it
without manual intervention). Update Badge::calculateClassAttribute()
to emit the new family, and update BadgeTest expectations to match.

This was a gap in PR #69 — that PR updated almost all components for
BS5 but missed Badge's color-class emission.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
ModalTest, ImageModalTest, ModalBuilderTest, and OutputPageParserOutputTest
all assumed the old deferred-content pattern — they mocked
parserOutputHelper->injectLater() to capture the modal markup separately
from the parser-hook return value. After the inline-emission refactor in
5f65471 the modal markup is concatenated into the parse output and
injectLater() no longer exists.

Updates:
- ModalTest::testCanRender asserts the parse output equals the trigger
  HTML and the modal HTML concatenated.
- ImageModalTest::testCanParseImage asserts the expected modal markup is
  a substring of the parse result.
- ModalBuilderTest::testCanParse asserts ModalBuilder::parse() returns
  trigger HTML concatenated with the modal HTML.
- OutputPageParserOutputTest rewritten: asserts that OutputPage::addModules
  is called for ext.bootstrap.scripts + the four BC component JS modules,
  plus vector-fix when the Vector skin is active. Drops the old expectation
  that the hook calls addHTML with deferred content (the deferred-content
  branch was removed).

96 of BC's PHPUnit tests now pass on bs5-followup (excluding the Lua test
files, which require Scribunto in the test environment).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Sweep through docs/components.md and the 12 per-component .md files
under docs/components/ to bring them in line with what BS5 BC actually
emits, plus a few related docs touch-ups:

- All external 'see also' links pointing to https://getbootstrap.com/docs/4.1/
  bumped to /docs/5.3/. Jumbotron's outdated direct link now points at the
  BS5 migration guide entry instead (BS5 doesn't have a jumbotron page).
- jumbotron.md: prepended a deprecation banner noting the BS5 removal and
  the utility-class approximation BC now emits. Also clarified that the
  BS4-era 'enlarges fonts' behaviour does not survive the utility-class
  reimplementation (apply display-* utilities for that look).
- components.md: inline note next to the Jumbotron list entry calling out
  the deprecation.
- button.md / badge.md / modal.md / popover.md: clarified next to the
  color='default' list item that this maps to 'secondary' under Bootstrap 5
  (matches the runtime behaviour Button.php, Modal.php, Popover.php
  already implemented).
- known-issues.md: top-of-file note that the long-standing modal/popover/
  tooltip 'stop working after a cache purge on MW 1.43.3' class of issues
  (upstream #68) was resolved in 6.0 by the inline-emission refactor.
- release-notes.md: 6.0.0 entry expanded with the bug-fix section
  describing the deferred-content → inline-emission rewrite, the
  DOMContentLoading typo fix, the new modal JS module, and the
  OutputPageParserOutput addModules change. Also tweaked the badge bullet
  to mention the text-bg-<color> family and the default→secondary mapping.
- migration-guide.md: bumped 'mediawiki/bootstrap 6.x-dev' reference to
  '^6.0' to match the released constraint.

Code-level: Badge::calculateClassAttribute now maps color='default' to
'secondary' for backward-compat (matches Button/Modal/Popover behaviour).
BadgeTest still 7/7.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@malberts
Copy link
Copy Markdown
Collaborator Author

Visual evidence

Captured on the 4-stack visual-regression harness (MediaWiki 1.43.8). Wikitext source for both pages is in the PR's findings / available on request.

1. Modal click actually opens on the BS5 candidate (closes #68)

<bootstrap_modal text="Open the modal" header="Modal Title" footer="Footer text">…</bootstrap_modal> on :8432 (Chameleon + BS5 candidate). Click on the trigger; modal renders with backdrop, dialog, header/body/footer; .modal.fade.show + .modal-backdrop both present in DOM.

BC-modal-open-bs5-chameleon

2. Comprehensive per-attribute matrix — Chameleon BS4 baseline vs BS5 candidate

BCTestMatrix page (~75 attribute permutations: 8 colours × every component, all sizes, all popover/tooltip placements & triggers, outline/pill/active/disabled variants, custom-class/style passthrough). Full-page screenshots, same wikitext on both stacks.

Chameleon BS4 baseline (:8431):

8431-bctest-matrix

Chameleon BS5 candidate (:8432):

8432-bctest-matrix

Visible differences are exactly the accepted BS5 style-trend evolution (brighter primary blue / cyan, slightly larger border-radius, lighter bg-body-tertiary jumbotron, BS5 SVG-style × close buttons). The color="default" button + modal trigger render as a properly-styled btn-secondary on BS5 where they were unstyled plain text on BS4 (BS4 4.6+ has no .btn-default).

3. Cross-skin parity — Medik BS5 candidate

Same BCTestMatrix on :8434 (Medik master, BS5 lib 5.3.8). BC renders identically to the Chameleon-BS5 stack — confirms BC isn't skin-coupled and the migration works for every BS5-aware skin.

8434-bctest-matrix

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Tooltip, Popover and Modal stop working after a cache purge on MW 1.43.3 Move to Bootstrap 5

1 participant