Draft: Replace the render-function bootstrap-vue implementation with Vue-3-portable templates

Replace the render-function bootstrap-vue implementation with Vue-3-portable templates

⚠️ Disclaimer: 100% AI-generated

Every line of code, test, and this description was written by Claude (Opus 5.5) in Claude Code, directed and supervised by @xanf, with no hand-written code. Correctness claims rest on machine-checked evidence (markup/event parity fixtures rendered by main, the unit suites, Storybook pixel comparison, the Cypress suite, and GitLab's full jest + rspec suites via the integration MRs), not on human review.

This is one draft MR on purpose: it carries the whole series so it can be evaluated end to end. Every commit is one future small MR (see Splitting strategy). Status: complete. 87 commits on main bbb9a511ed; the vendored src/vendor/bootstrap-vue directory is gone.

What this does

Removes the vendored bootstrap-vue (BSV) render-function / functional / mixin implementation from @gitlab/ui and replaces it with Options-API SFCs with <template>s, written so that the library can move to Vue 3 without @vue/compat with minimal changes.

  • The vendored tree is deleted. No importer is left, dist/vendor is gone and npm pack ships no bootstrap-vue path. The derived SCSS stays in src/scss/bootstrap_vue, with the BootstrapVue license notice next to it. vue-functional-data-merge is dropped (popper.js and portal-vue stay, src uses them), and so are the ui:bootstrap-vue CI jobs.
  • Where the code went. Dropdown and its items, tabs, form select, tooltip, popover and the tooltip directive are inlined into their GlX SFC. Modal, table, table lite and form group keep an internal template SFC with the BSV name (BModal, BTable, BTableLite, BFormGroup), so consumer test contracts (stubs, findComponent({ name }), $children[0]) keep working.
  • Pure refactor. No prop, event or slot is removed. 45 changesets: 41 patch, 4 minor (new public methods and the GlTable items provider type), no major. Vue 2 output is identical modulo comment placeholders and the deviations under Known differences; under @vue/compat behaviour only moves towards Vue 2.
  • Size. 533 files, +49 434 / −51 984 against main.

Companion MRs: the native Vue 3 package !6551 and the GitLab MRs under Consumer follow-ups.

How the code stays portable

  • One version shim, src/utils/vue_utils.js, holds every Vue-version-specific line: attrs and listeners (getAttrs, getListeners, getNonFallthroughListeners, getUndeclaredRootListeners, getRootClassStyle incl. compat .native routing, getComponentRootClassStyle), slots (getSlot, hasSlot, getNormalizedSlot, SlotOutlet, …), uid, reactive set/delete, ARRAY_WATCH_OPTIONS, scope id, isNativeVue3, defineDirective, mountComponent, transition class props, owner events, and the vModelDynamic backfill from the vendored vue.js. Branches select at runtime; under @vue/compat the native Vue 3 branch runs, so the existing Vue 3 jest lane exercises it.
  • Root event bus (bv::*, ~110 call sites in GitLab) isolated in src/utils/root_events.js (Vue 2/compat: the $root bus; pure Vue 3: a per-app emitter).
  • src/utils/vue_teleport.js: Teleport shim. Its Vue 2 branch is the ported BVTransporter; its Vue 3 branch renders the built-in Teleport once the target exists and, like portal-vue, nothing (and no warning) before.
  • Render functions left: only inside the shims, namely the Teleport shim and SlotOutlet. The Vue 2 Teleport branch and SlotOutlet are deleted at the Vue 3 switch (built-in Teleport, native <slot>).
  • Lint guard: PORTABLE_VUE_FILES in eslint.config.mjs (39 entries, every file that replaced vendored code) forbids render functions, functional, mixins, extends and Vue-2-only instance APIs outside the shims.
  • Template rules, avoided explicitly: keyed <template v-for> (the two compilers disagree), .native, .sync, falsy attribute bindings, accidental whitespace text nodes (strict-whitespace prettier override where markup is whitespace-sensitive), refs inside v-for (normalised: unpatched compat ignores refInFor), slot presence cached in computeds, template refs named like state.

Evidence (tip 9224b1a3bb)

Layer Result
Unit suite, Vue 2 6255 passed / 1 skipped
Unit suite, Vue 3 @vue/compat 6434 passed / 3 skipped
Markup + event parity vs fixtures rendered by main (Vue 2), 1615 scenarios 0 strict differences; 24 accepted (intended, see Known differences); 101 HTML-snapshot-only (comment placeholders)
Same, Vue 3 compat lane 0 strict differences; 581 scenarios converge to Vue 2; 43 accepted; 814 snapshot-only
Hybrid lane (Vue 2-compiled templates on the unpatched compat runtime) 0 new render errors; 38 errors that main has are gone
Storybook pixel comparison, 491 stories × vue2 / vue3 / hybrid, 1 px threshold 491/491 ×3
Cypress e2e (vue2 / vue3 / hybrid) 77/77 ×3
Build and dist yarn build ok; 248 JS files, none under vendor/, every relative import resolves; compared with main's dist only dist/vendor (removed), 9 utility modules and 4 Storybook-only tailwind.css rules differ; yarn install --frozen-lockfile ok
Lint and messages eslint, prettier, stylelint (0 errors), markdownlint clean; subjects and body lines ≤ 72; Danger emoji check clean
design.gitlab.com CI green: https://gitlab.com/gitlab-org/gitlab-services/design.gitlab.com/-/pipelines/2977268471 (only the allow-failure code_quality infra job fails)
GitLab full jest + rspec green, see GitLab

Every commit passes jest (both lanes), parity, the hybrid lane and lint on its own.

GitLab

GitLab with this series' package (ui:build_package of this MR) and the consumer changes listed under Consumer follow-ups, with pipeline:run-all-jest and pipeline:run-all-rspec: gitlab-org/gitlab!264170, pipeline https://gitlab.com/gitlab-org/gitlab/-/pipelines/2977009348.

Suite Result
jest (Vue 2 11/11, Vue 3 compat 11/11, with fixtures 2 + 2, integration 1 + 1) all green
rspec (unit, integration, migration, background migration, frontend fixtures, clickhouse/es9 variants) all green
rspec system pg17 / rspec-ee system pg17 27/27, 15/15
e2e child pipelines green

Red jobs, unrelated to this change and red on every integration MR: untamper-my-lockfile (cannot parse the package tarball URL in yarn.lock), the start-as-if-foss bridge, and allow-failure bundle-size-review / pajamas_adoption. Commit 87 is spec-only and does not change the package.

Native Vue 3

Package !6551 (packages/gitlab-ui-vue3): derived from this branch by a sync script plus codemods (lifecycle renames, compatConfig pins, shim on native Vue 3), jest (vue3-jest + VTU 2) and rollup, no @vue/compat. Synced from this series (no vendored code left):

Check Result
Native jest 195/195 suites, 6098 passed / 0 failed; the 32 skips are the specs' own native gates (.native, Vue.extend, $root.$on)
Hand-edited specs 1 (base_dropdown.spec.js); every other spec runs as written here
Parity vs the Vue 2 fixtures (140 modules) 97 identical, 43 differ; every difference is what this series renders under @vue/compat or inherent to native Vue 3 (.native, attributes bound to false, legacy self-events)
Build and CI 247 dist files, ES2018 check ok; ui-vue3:unit_tests and ui-vue3:build green

GitLab Vue 3 MR gitlab-org/gitlab!264182, the analogue of gitlab-org/gitlab!246445 (closed) on the new package (no test-runner migration): assets compiled against the package, pipeline:run-rspec-vue3, a native jest lane. Pipeline https://gitlab.com/gitlab-org/gitlab/-/pipelines/2977350195 is green, against 246445's latest https://gitlab.com/gitlab-org/gitlab/-/pipelines/2688844024:

Lane gitlab MR 264182 (this rewrite) gitlab MR 246445 (hand-ported package)
compile-test-assets vue3 (+ production vue3 compiles) green green
rspec system pg17 vue3 27/27 shards 29/32
rspec-ee system pg17 vue3 15/15 shards 15/16
native jest (jest vue3 gitlab-ui-vue3 + fixtures) 13/13 shards not in CI
compat jest baseline 13/13 shards 13/13

GitLab-side changes in that MR (Vue 2 behaviour unchanged): RefSelector does not forward its own input/hide listeners to the listbox (on Vue 3 they would fall through to the dropdown root), note_agent_actor_line and tooltip_on_truncate call the directive hooks of the running Vue version, plus the spec changes G1–G7.

Splitting strategy

Each row is one future MR, and every commit is green on its own.

  • Order. Rows follow the branch order (git log --reverse main..), which is one valid merge order. After the foundation (rows 1–15, needed by every later row), the families can be reviewed in parallel.
  • ID. Family prefix and number (F foundation, FS/FG forms, TS tabs, DD dropdown, MD modal, TT tooltip, TB table, PX native Vue 3 portability, X removal).
  • Depends on. What a row needs beyond the foundation: shim helpers another family added, or files an earlier commit changed.
  • Ship together. MD4 + MD5 (MD4 alone leaves a compat-only portal divergence), TB7 + TB8 (shared changeset wording), TS3 + TS4 (TS3 drops an empty nav-slot <!----> that TS4 restores).
  • GitLab first. DD12 needs G2 merged in GitLab first.
  • GitLab MR needed? G1–G7 are in gitlab-org/gitlab!264170.

Foundation: utilities out of the vendored tree, version shim, root events, mount helper, Teleport shim (rows 1–15)

# ID Commit (future MR) Files, +/− Depends on GitLab MR needed?
1 F1 Lint migrated components for Vue 3 portability 50a2100aa9 1, +98/−0 — no
2 F2 Move bootstrap-vue spec helpers to tests/__helpers__ eec2eba4b9 26, +75/−68 — no
3 F3 Remove dead bootstrap-vue config, plugin and locale utilities 27b22d8804 14, +15/−660 — no
4 F4 Use existing src/utils equivalents inside vendored bootstrap-vue 56025a0a29 21, +21/−294 — no
5 F5 Move bootstrap-vue DOM helpers to src/utils/dom_utils.js 07b1c224c4 31, +956/−753 F3, F4 no
6 F6 Move BvEvent to src/utils 33b279d36a 10, +262/−131 F5 no
7 F7 Move shared toInteger and key-code helpers to src/utils 406823d1f4 24, +74/−119 F4–F6 no
8 F8 Relocate bootstrap-vue SCSS into src/scss/bootstrap_vue 4b6daec7dc 18, +27/−39 — no
9 F9 Add Vue 2/3 version shim helpers to vue_utils 26d151886e 8, +984/−46 — no
10 F10 Add root event bus adapter 66d3aaa1fc 11, +762/−13 F9 no
11 F11 Add programmatic mount helper and use it for GlToast 249acebf14 6, +330/−18 F9, F10 no
12 F12 Add portable transition class helpers and fix GlToast leave class 3419202a50 6, +350/−11 F11 no
13 F13 Replace BVTransporter with a Teleport shim e756c8cda0 9, +650/−341 F9, F11 no
14 F14 Move bootstrap-vue event listener helpers to src/utils 2b015e34ae 32, +277/−216 F5, F10, F13 no
15 F15 Route compat native listeners to the component root in the version shim c1fb3d6c95 2, +345/−16 F9 no

Forms: GlFormSelect and BFormGroup (FS, FG)

# ID Commit (future MR) Files, +/− Depends on GitLab MR needed?
16 FS3 Make GlFormSelect portable 7bf0c2fcc3 4, +157/−13 — no
17 FS4 Delete orphaned vendored form mixins b241ac54a8 8, +5/−212 FS3 no
18 FG1 Inline BFormText and BForm(In)validFeedback into BFormGroup fc73ef576e 16, +230/−830 — no
19 FG2 Replace BCol and BFormRow with plain elements in BFormGroup 97642cb077 18, +226/−1354 FS4, FG1 no
20 FG3 Rewrite BFormGroup as an internal template SFC f715661771 25, +2010/−2095 FG2 no
53 FG4 Route GlForm and GlFormGroup listeners through the version shim f01687bdba 8, +448/−8 FG3, TB15 no

Tabs (TS)

# ID Commit (future MR) Files, +/− Depends on GitLab MR needed?
21 TS1 Add GlTabs and GlTab behaviour specs ported from vendored specs afa0152b3c 2, +1264/−2 — no
54 TS2 Make GlScrollableTabs listener and attr forwarding portable 5984426178 4, +140/−8 TB15 no
55 TS3 Inline BTabs into GlTabs 518b6e1ed9 9, +1081/−89 TS1, MD5 · ship with TS4 yes: G5
56 TS4 Inline BTab into GlTab 5732eff246 11, +1072/−117 TS3, TB15 yes: G4, G6
57 TS5 Delete vendored bootstrap-vue tabs 34c9de8e83 15, +42/−2849 TS4 no

Dropdown (DD)

# ID Commit (future MR) Files, +/− Depends on GitLab MR needed?
22 DD1 Drop dead Vue.extend wrapper from GlDropdown 40fd329038 2, +88/−20 — no
23 DD2 Delete unused vendored BDropdownGroup 5eb8f29aa4 7, +2/−256 — no
24 DD3 Inline BDropdownDivider into GlDropdownDivider 4662493c01 10, +250/−107 DD2 no
25 DD4 Inline BDropdownHeader into GlDropdownSectionHeader 5398514f6f 10, +330/−155 DD3 no
26 DD5 Inline BDropdownText into GlDropdownText a35838fde9 10, +314/−136 DD4 no
27 DD6 Inline BDropdownForm and BForm into GlDropdownForm cbf3ed943a 16, +442/−708 FG2, DD5 no
28 DD7 Remove .native listeners on GlDropdownItem in GlFormCombobox d4590294d8 3, +145/−7 — no
29 DD8 Add public updatePopper method to GlDropdown bd6732968b 3, +97/−0 DD1 adds the API G2 uses
30 DD9 Inline BDropdownItemButton into GlDropdownItem f25d8b9f67 9, +581/−288 DD6 no
58 DD10 Inline BDropdownItem link rendering into GlDropdownItem 39cb315ebe 15, +1086/−343 DD9 no
66 DD11 Port the vendored BDropdown spec onto GlDropdown a32ec12e66 2, +1353/−1178 DD8, DD10 no
70 DD12 Inline BDropdown and its mixins into GlDropdown ac7a785913 17, +871/−1305 DD11, TT6 yes: G2 first
71 DD13 Replace vendored BButton in GlDropdown with native toggle markup dcdd0dfd16 4, +826/−39 DD12 no (API narrowing, no usage)
72 DD14 Remove leftover vendored dropdown files and move dropdown styles 5cb71d1aa6 21, +104/−832 DD13 no

Modal (MD)

# ID Commit (future MR) Files, +/− Depends on GitLab MR needed?
31 MD1 Move the modal manager out of vendored bootstrap-vue 11f19e8e92 7, +477/−209 — no
32 MD2 Route BModal root-bus traffic through the root event adapter f63a911ce4 3, +183/−12 MD1 no
33 MD3 Rewrite v-gl-modal directive without bootstrap-vue 7cf4d3a8b0 9, +626/−363 — no
34 MD4 Drop scoped-style, attrs-cache and id mixins from BModal a4fb865194 3, +196/−22 MD2 · ship with MD5 no
35 MD5 Rewrite BModal as an internal template SFC 67c1b9753e 23, +3837/−4139 MD3, MD4 yes: G1 (Vue 3 jest)
59 MD6 Make GlModal's template portable fbbbb46a30 7, +442/−30 MD5, TB15 no
60 MD7 Remove leftover vendored modal artifacts ea2e03f57b 22, +326/−1328 MD6 no

Tooltip and popover (TT)

# ID Commit (future MR) Files, +/− Depends on GitLab MR needed?
36 TT1 Add GlPopover and GlTooltip characterization specs d6059db982 3, +1880/−1 — no
37 TT2 Replace tooltip_mixin with open, close, enable and disable methods 54abd7aaac 10, +366/−59 TT1 optional (pure Vue 3)
38 TT3 Extract popper positioning helpers into popper_utils b1827147ca 5, +352/−89 — no
39 TT4 Extract tooltip and popover DOM helpers into src/utils 527a559d6e 5, +707/−122 — no
40 TT5 Route tooltip root-bus traffic through the root event adapter 5a9e568a81 3, +254/−14 TT2, TT4 no
41 TT6 Route tooltip target-owner shown auto-hide through the version shim bc0c786796 9, +404/−6 TT5 no
61 TT7 Replace BVPopper and BVTooltipTemplate with a template SFC 0857b993e0 12, +924/−49 TT6, MD5 no
65 TT8 Extract BVTooltip into a portable controller SFC 7bb92ec93e 23, +1215/−956 TT3, TT7 no
73 TT9 Inline BTooltip into GlTooltip 13ee620e3e 12, +900/−70 TT2, TT8 no
74 TT10 Inline VBTooltip into GlTooltipDirective df1023c497 15, +676/−1245 MD3, TT9 pure Vue 3 only (in gitlab MR 264182)
77 TT11 Reimplement GlPopover on the shared tooltip controller 30b7989afc 23, +744/−3672 TT10 no
78 TT12 Remove vendored tooltip leftovers and move tooltip and popover styles a153a8af68 16, +84/−936 TT11 no

Table (TB; TB10–TB14 are spec-only, written against the old implementation)

# ID Commit (future MR) Files, +/− Depends on GitLab MR needed?
42 TB1 Delete unused BTableSimple and the vendored table barrel fdb811dd43 8, +25/−658 — no
43 TB2 Move table DOM helpers and field constants to table_utils 8b6c99fa1e 12, +377/−88 — no
44 TB3 Move table field and row helpers to table_utils f292aa76f7 24, +585/−337 TB2 no
45 TB4 Move default sort compare to table_utils and use native stable sort 52eafc0301 11, +591/−200 TB3 no
46 TB5 Extract table filtering logic to table_utils 3ad0210e9b 9, +371/−143 TB4 no
47 TB6 Extract table sorting logic to table_utils 5cd63611d4 5, +871/−151 TB5 no
48 TB7 Extract table row selection logic to table_utils 215b8b45d6 7, +674/−82 TB6 · ship with TB8 no
49 TB8 Expose row selection and refresh methods on GlTable 52460b338a 5, +233/−1 TB7 optional (methods instead of $children[0])
50 TB9 Accept items provider functions in GlTable items prop 82bdb62c62 4, +211/−8 TB8 no
51 TB10 Test GlTable pagination, primary key and item formatters directly 9027068160 3, +572/−0 — no
52 TB15 Make GlTable and GlTableLite slot and listener forwarding portable 5b2a3ee82b 8, +547/−31 TB9 no
62 TB11 Test GlTable filtering directly f6201ec9e8 1, +783/−0 — no
63 TB12 Test GlTable sorting directly 0c8d08ad58 1, +754/−0 — no
64 TB13 Test GlTable row selection directly f9dd872a4f 1, +1089/−0 — no
67 TB14 Test GlTable busy state and items provider directly 39886bf17d 1, +954/−0 — no
68 TB16 Render table header and footer from a template SFC 9a4b5083b6 18, +1081/−590 TB15, MD5 yes: G3
75 TB17 Render table body rows from a template SFC f5941e7d70 27, +1551/−1233 TB10, TB13, TB16 yes: G7
76 TB18 Move busy, top, bottom and empty rows into the body template f877128f6d 16, +499/−670 TB17 no
79 TB19 Replace BTableLite with a template SFC core and shared table element 83bd39728c 14, +1265/−76 TB18 no
82 TB20 Replace BTable with a template SFC core ff4c14a5b7 27, +1849/−2045 TB11–TB14, TB19 no
83 TB21 Relocate vendored table specs and remove remaining table vendor files 8eecf3b352 47, +3308/−11252 TB20 no

Native Vue 3 portability (PX)

# ID Commit (future MR) Files, +/− Depends on GitLab MR needed?
69 PX1 Make specs portable to native Vue 3 4c95e0923b 7, +29/−19 — no
80 PX2 Forward GlButton listeners through the version shim 9b67bfb696 6, +294/−17 — no (events declared on native Vue 3 only via nativeEmits())
81 PX3 Declare emitted events that also fall through on Vue 3 6ad591cd29 20, +254/−2 — no (nativeEmits())

Removal (X)

# ID Commit (future MR) Files, +/− Depends on GitLab MR needed?
84 X1 Delete vendored BButton, BButtonClose and BLink f480c77837 14, +8/−2010 MD5, TS3, DD10, DD13 no
85 X2 Remove orphaned vendored utilities and the element-to-instance registry 5fbcb62365 71, +165/−3148 X1, all families no
86 X3 Remove the vendored bootstrap-vue directory d0d02bf2c5 32, +47/−2525 X2 no
87 PX4 Make the remaining specs portable to native Vue 3 9224b1a3bb 18, +367/−275 X3 no (spec-only)

Consumer follow-ups (GitLab / duo-ui)

gitlab-org/gitlab!264170 (Draft, pipeline:run-all-jest, pipeline:run-all-rspec, pipeline::tier-3): the integration commit plus spec/code fixes. Each fix also passes with the current @gitlab/ui and can land on master before the bump; the exception is the SLA snapshot line of G4.

GitLab commit For Why
G1 Check hidden lazy modals by text instead of empty HTML MD5 Vue 3 jest lane: VTU 2 serializes the hidden modal's placeholder comment
G2 Use GlDropdown public methods instead of its internal dropdown ref DD12 Blocking. 4 sites used the private $refs.dropdown. Without the fix, the alert sidebar "Edit" assignee button silently stops opening its menu (no error, no spec). Falls back on releases without updatePopper()
G3 Find GlTable header cells by element instead of component TB16 header cells are elements now
G4 Check lazy GlTab stubs independently of the prop declaration TS4 GlTab declares the BTab props, so stubs render lazy="true" / active="true"
G5 Mount real tabs in the DevOps adoption app spec TS3 GlTabs' own logic now runs in the shallow-mounted spec
G6 Use Vue 2 transition class attribute names in snapshots TS4 the serializer maps enterfromclass to enterclass, so one snapshot serves both jest lanes
G7 Find GlTable body rows by element instead of component TB17 body rows are elements now (three AI settings tables, alert management table)

gitlab-org/gitlab!264182 (Draft): the native Vue 3 lanes on the package from !6551. It also carries the GitLab change needed only for pure Vue 3: tooltip_on_truncate.js calls the GlTooltipDirective hooks of the running Vue version (TT10), and the RefSelector and note_agent_actor_line changes above.

Not needed for the bump (before a pure Vue 3 switch):

  • TT2. Use open() / close() / enable() / disable() of GlTooltip/GlPopover instead of $refs.x.$emit('open' | …) (tooltips.vue, observability_feedback.vue, ee compliance filters.vue) and add them to the jest mocks. $emit keeps working on Vue 2 and compat.
  • TB8. Call selectRow(), …, refresh() on GlTable instead of $children[0] (which still works).

No change needed: GitLab's group_name_and_path_spec finds { name: 'BFormGroup' } and the core keeps that name; duo-ui uses none of the narrowed or removed APIs (DD13 toggle-attrs keys, the bvEventRoot fallback).

design.gitlab.com follow-ups (separate MRs):

  • Pre-existing. GlButton, GlFormInput, GlFormTextarea and GlLink pass BSV option names (propagation, immediatePropagation) to stopEvent, which ignores them. GlCollapse binds Vue 2 transition class names.
  • Upstream. Tell the author of 39561c88 about its Vue 2 side effects (Known differences).
  • After the Vue 3 switch. Delete the Vue 2 Teleport branch and SlotOutlet (native <slot>; also removes the per-cell instance cost on Vue 3: +37% mount / +24% re-render on compat with all 8 GlTable columns slotted). Forward deactivated() of GlTooltip/GlPopover in <keep-alive> to forceHide(). GlPopover renders its slots twice per update on Vue 3. Decide whether GlFormGroup forwards non-native listeners (Vue 2 never delivered them).
  • Test infrastructure. Add a real-browser @vue/compat e2e check for nested tooltips and tab panels (jsdom cannot see DOM-anchor and visibility differences).

Known differences

Vue 2 deviations from main

  • DD13: GlDropdown toggle-attrs. Keys that named BButton props (href, to, pressed, rel, target) no longer change the toggle; only type applies. event and router-tag render as DOM attributes on link toggles, and a camelCase + kebab-case duplicate renders the kebab one. No GitLab or duo-ui usage.
  • TB18: variant in a static tbodyTrAttr object now applies to the busy, top, empty and bottom rows on every render. BSV deleted the key from the object at the first render, so the variant applied only once.
  • GlTooltip / GlPopover slot content (TT9, TT11). main (since 39561c88) stops following v-slot content while the tooltip is open and shows comments for comment-only v-slot content. Here, on Vue 2, both follow the slot while open and fall back to the title prop for comment-only content; the title-prop fallback of that commit is kept. A default container set with setGlTooltipDefaultContainer() after mount applies on the next prop, title or content change (GitLab sets it once at boot).

Intended changes visible on Vue 2

The other 20 accepted Vue 2 parity entries:

  • Slot presence is checked at render time, so slots added or removed after mount take effect: GlFormGroup label description (FG4), GlModal modal-ok / modal-cancel / modal-footer (MD6), GlTable head slots (TB15).
  • An items-provider function no longer logs a prop-type warning (TB9); @input.once on GlTabs runs once (TS3); buttonClass is a declared GlDropdownItem prop, so its junk attribute spelling on link items changes (DD9).
  • Other Vue 2 fixes: a thAttr style no longer drops thStyle (TB16); a non-static GlModal destroyed in the update that opens it no longer leaves a copy in document.body (F13); selectAllRows() no longer throws RangeError on very large tables.

Test contracts and snapshots only

  • Inlined components declare the BSV props they used to pass on (GlTab, GlTabs, the GlDropdown family, GlTooltip, GlPopover): those keys leave $attrs, and stubs render them as props. Components that now emit events themselves show them in wrapper.emitted() (GlTab, GlTabs, GlDropdownItem links). Components rewritten with declared emits (GlDropdown, tabs, modal and table cores, tooltip, popover, form select) warn on Vue 3 / compat when code $emits an undeclared event on them (no GitLab usage); GlButton and the 14 PX components declare theirs on native Vue 3 only.
  • shallowMount of the real GlTooltip renders a comment instead of a stubbed BTooltip; the BTr/BTd/BTh/BTbody components are gone (find rows and cells by element); a hidden lazy GlModal serializes as <!----> under VTU 2.
  • HTML only (101 snapshot-only Vue 2 parity scenarios): comment placeholders between table rows and in some slots, style strings in tdAttr / thAttr / tbodyTrAttr serialized like other styles, shifted __BVID__ numbers.

Packaging

  • vue-functional-data-merge is no longer a dependency. bootstrap-vue's license notice moved to src/scss/bootstrap_vue/LICENSE (that SCSS still derives from bootstrap-vue). dist/tailwind.css loses 4 utilities only Storybook used.

Compat convergences

581 compat parity scenarios now match Vue 2 (details per changeset):

  • .native listeners (also with capture, once, key modifiers) reach the root element of inheritAttrs: false components; listeners added after mount are called; GlTabs shows its empty slot again.
  • Transitions use the class prop names of the running Vue, so enterclass / leaveclass no longer leak as attributes; GlToast fades out from show; GlFormSelect change carries the value.
  • Toasts, tooltips and popovers mount into the calling app instead of separate data-v-app wrappers; tooltips and popovers are removed right away on destroy and close when a GlDisclosureDropdown or GlCollapsibleListbox opens; the tooltip engine logs no compat deprecation warnings.
  • Table rows are focusable and keyboard-handled only with a row-clicked listener, and row listeners are fixed at the first render, as on Vue 2. $on listeners on the internal table do not get row events.

The 43 accepted compat entries are the 20 shared intended changes plus 23 partial convergences: tooltip and popover slot content while open, container forms, the tab link disabled attribute, tab pane transition attributes, an empty foot slot of GlTableLite.

🤖 Generated with Claude Code

Edited by Illya Klymov

Merge request reports

Loading
Loading