From f4ac4459f8317bd5eeff7d4809f9cb0991c8efd9 Mon Sep 17 00:00:00 2001
From: OS-jacobbell <228905018+OS-jacobbell@users.noreply.github.com>
Date: Tue, 14 Apr 2026 07:27:28 -0600
Subject: [PATCH 01/11] fix(checkbox): show labels after page navigation
(#31062)
Issue number: resolves #31052
---------
## What is the current behavior?
After a page navigation, ion-checkbox's `onslotchange` event fires
before the element's `textContent` has been updated. It is called again
after `textContent` becomes readable on Safari, but is not called again
after the `textContent` becomes readable on Chrome and Firefox.
## What is the new behavior?
- Uses `MutationObserver` instead of `onslotchange` and fires
specifically on character data changes. This ensures `hasLabelContent`
is up to date.
- `MutationObserver` does not fire on load, so `hasLabelContent` is
initialized in `connectedCallback`
## Does this introduce a breaking change?
- [ ] Yes
- [X] No
---
core/src/components/checkbox/checkbox.tsx | 68 ++++++++++++-----------
1 file changed, 37 insertions(+), 31 deletions(-)
diff --git a/core/src/components/checkbox/checkbox.tsx b/core/src/components/checkbox/checkbox.tsx
index 8a25b9200d5..99cb13c474a 100644
--- a/core/src/components/checkbox/checkbox.tsx
+++ b/core/src/components/checkbox/checkbox.tsx
@@ -151,44 +151,54 @@ export class Checkbox implements ComponentInterface {
connectedCallback() {
const { el } = this;
- // Watch for class changes to update validation state.
if (Build.isBrowser && typeof MutationObserver !== 'undefined') {
- this.validationObserver = new MutationObserver(() => {
- const newIsInvalid = checkInvalidState(el);
- if (this.isInvalid !== newIsInvalid) {
- this.isInvalid = newIsInvalid;
- /**
- * Screen readers tend to announce changes
- * to `aria-describedby` when the attribute
- * is changed during a blur event for a
- * native form control.
- * However, the announcement can be spotty
- * when using a non-native form control
- * and `forceUpdate()`.
- * This is due to `forceUpdate()` internally
- * rescheduling the DOM update to a lower
- * priority queue regardless if it's called
- * inside a Promise or not, thus causing
- * the screen reader to potentially miss the
- * change.
- * By using a State variable inside a Promise,
- * it guarantees a re-render immediately at
- * a higher priority.
- */
- Promise.resolve().then(() => {
- this.hintTextId = this.getHintTextId();
- });
+ this.validationObserver = new MutationObserver((mutations) => {
+ // Watch for label content changes
+ if (mutations.some((mutation) => mutation.type === 'characterData' || mutation.type === 'childList')) {
+ this.hasLabelContent = this.el.textContent !== '';
+ }
+ // Watch for class changes to update validation state.
+ if (mutations.some((mutation) => mutation.type === 'attributes' && mutation.target === el)) {
+ const newIsInvalid = checkInvalidState(el);
+ if (this.isInvalid !== newIsInvalid) {
+ this.isInvalid = newIsInvalid;
+ /**
+ * Screen readers tend to announce changes
+ * to `aria-describedby` when the attribute
+ * is changed during a blur event for a
+ * native form control.
+ * However, the announcement can be spotty
+ * when using a non-native form control
+ * and `forceUpdate()`.
+ * This is due to `forceUpdate()` internally
+ * rescheduling the DOM update to a lower
+ * priority queue regardless if it's called
+ * inside a Promise or not, thus causing
+ * the screen reader to potentially miss the
+ * change.
+ * By using a State variable inside a Promise,
+ * it guarantees a re-render immediately at
+ * a higher priority.
+ */
+ Promise.resolve().then(() => {
+ this.hintTextId = this.getHintTextId();
+ });
+ }
}
});
this.validationObserver.observe(el, {
attributes: true,
attributeFilter: ['class'],
+ characterData: true,
+ childList: true,
+ subtree: true,
});
}
// Always set initial state
this.isInvalid = checkInvalidState(el);
+ this.hasLabelContent = this.el.textContent !== '';
}
componentWillLoad() {
@@ -267,10 +277,6 @@ export class Checkbox implements ComponentInterface {
ev.stopPropagation();
};
- private onSlotChange = () => {
- this.hasLabelContent = this.el.textContent !== '';
- };
-
private getHintTextId(): string | undefined {
const { helperText, errorText, helperTextId, errorTextId, isInvalid } = this;
@@ -387,7 +393,7 @@ export class Checkbox implements ComponentInterface {
id={this.inputLabelId}
onClick={this.onDivLabelClick}
>
-
+
{this.renderHintText()}
From b431b4c94450b1176fa4e97792861b66da88741c Mon Sep 17 00:00:00 2001
From: ionitron
Date: Wed, 15 Apr 2026 18:12:24 +0000
Subject: [PATCH 02/11] v8.8.4
---
CHANGELOG.md | 13 +++++++++++++
core/CHANGELOG.md | 13 +++++++++++++
core/package-lock.json | 6 +++---
core/package.json | 2 +-
lerna.json | 2 +-
packages/angular-server/CHANGELOG.md | 8 ++++++++
packages/angular-server/package-lock.json | 8 ++++----
packages/angular-server/package.json | 4 ++--
packages/angular/CHANGELOG.md | 8 ++++++++
packages/angular/package-lock.json | 8 ++++----
packages/angular/package.json | 4 ++--
packages/docs/CHANGELOG.md | 8 ++++++++
packages/docs/package-lock.json | 6 +++---
packages/docs/package.json | 2 +-
packages/react-router/CHANGELOG.md | 8 ++++++++
packages/react-router/package-lock.json | 8 ++++----
packages/react-router/package.json | 4 ++--
packages/react/CHANGELOG.md | 8 ++++++++
packages/react/package-lock.json | 8 ++++----
packages/react/package.json | 4 ++--
packages/vue-router/CHANGELOG.md | 8 ++++++++
packages/vue-router/package-lock.json | 8 ++++----
packages/vue-router/package.json | 4 ++--
packages/vue/CHANGELOG.md | 8 ++++++++
packages/vue/package-lock.json | 8 ++++----
packages/vue/package.json | 4 ++--
26 files changed, 127 insertions(+), 45 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 0fae4854106..84abbbee409 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -3,6 +3,19 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
+## [8.8.4](https://github.com/ionic-team/ionic-framework/compare/v8.8.3...v8.8.4) (2026-04-15)
+
+
+### Bug Fixes
+
+* **checkbox:** show labels after page navigation ([#31062](https://github.com/ionic-team/ionic-framework/issues/31062)) ([f4ac445](https://github.com/ionic-team/ionic-framework/commit/f4ac4459f8317bd5eeff7d4809f9cb0991c8efd9)), closes [#31052](https://github.com/ionic-team/ionic-framework/issues/31052)
+* **datetime:** multiple month selected and flakiness display ([#31053](https://github.com/ionic-team/ionic-framework/issues/31053)) ([308aef5](https://github.com/ionic-team/ionic-framework/commit/308aef569d8c6ebc3ad2186bca6969da8e4b2a8d))
+* **tab-button:** update dark palette focused background color ([#31050](https://github.com/ionic-team/ionic-framework/issues/31050)) ([dec46b5](https://github.com/ionic-team/ionic-framework/commit/dec46b5d317080dd5d97dc056f0d8e6d4c8c45ac))
+
+
+
+
+
## [8.8.3](https://github.com/ionic-team/ionic-framework/compare/v8.8.2...v8.8.3) (2026-04-01)
diff --git a/core/CHANGELOG.md b/core/CHANGELOG.md
index 1fd2212efdf..ee45ffd629a 100644
--- a/core/CHANGELOG.md
+++ b/core/CHANGELOG.md
@@ -3,6 +3,19 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
+## [8.8.4](https://github.com/ionic-team/ionic-framework/compare/v8.8.3...v8.8.4) (2026-04-15)
+
+
+### Bug Fixes
+
+* **checkbox:** show labels after page navigation ([#31062](https://github.com/ionic-team/ionic-framework/issues/31062)) ([f4ac445](https://github.com/ionic-team/ionic-framework/commit/f4ac4459f8317bd5eeff7d4809f9cb0991c8efd9)), closes [#31052](https://github.com/ionic-team/ionic-framework/issues/31052)
+* **datetime:** multiple month selected and flakiness display ([#31053](https://github.com/ionic-team/ionic-framework/issues/31053)) ([308aef5](https://github.com/ionic-team/ionic-framework/commit/308aef569d8c6ebc3ad2186bca6969da8e4b2a8d))
+* **tab-button:** update dark palette focused background color ([#31050](https://github.com/ionic-team/ionic-framework/issues/31050)) ([dec46b5](https://github.com/ionic-team/ionic-framework/commit/dec46b5d317080dd5d97dc056f0d8e6d4c8c45ac))
+
+
+
+
+
## [8.8.3](https://github.com/ionic-team/ionic-framework/compare/v8.8.2...v8.8.3) (2026-04-01)
diff --git a/core/package-lock.json b/core/package-lock.json
index 01a06435345..8bef7f2f1f7 100644
--- a/core/package-lock.json
+++ b/core/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "@ionic/core",
- "version": "8.8.3",
+ "version": "8.8.4",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@ionic/core",
- "version": "8.8.3",
+ "version": "8.8.4",
"license": "MIT",
"dependencies": {
"@stencil/core": "4.43.0",
@@ -9829,4 +9829,4 @@
}
}
}
-}
+}
\ No newline at end of file
diff --git a/core/package.json b/core/package.json
index 47566399094..4321a90686f 100644
--- a/core/package.json
+++ b/core/package.json
@@ -1,6 +1,6 @@
{
"name": "@ionic/core",
- "version": "8.8.3",
+ "version": "8.8.4",
"description": "Base components for Ionic",
"engines": {
"node": ">= 16"
diff --git a/lerna.json b/lerna.json
index e79ac7702c2..ba3e4cc2dcf 100644
--- a/lerna.json
+++ b/lerna.json
@@ -3,5 +3,5 @@
"core",
"packages/*"
],
- "version": "8.8.3"
+ "version": "8.8.4"
}
\ No newline at end of file
diff --git a/packages/angular-server/CHANGELOG.md b/packages/angular-server/CHANGELOG.md
index 68ecf941df3..e92a2268e2e 100644
--- a/packages/angular-server/CHANGELOG.md
+++ b/packages/angular-server/CHANGELOG.md
@@ -3,6 +3,14 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
+## [8.8.4](https://github.com/ionic-team/ionic-framework/compare/v8.8.3...v8.8.4) (2026-04-15)
+
+**Note:** Version bump only for package @ionic/angular-server
+
+
+
+
+
## [8.8.3](https://github.com/ionic-team/ionic-framework/compare/v8.8.2...v8.8.3) (2026-04-01)
**Note:** Version bump only for package @ionic/angular-server
diff --git a/packages/angular-server/package-lock.json b/packages/angular-server/package-lock.json
index eaa09666b5b..62015e76228 100644
--- a/packages/angular-server/package-lock.json
+++ b/packages/angular-server/package-lock.json
@@ -1,15 +1,15 @@
{
"name": "@ionic/angular-server",
- "version": "8.8.3",
+ "version": "8.8.4",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "@ionic/angular-server",
- "version": "8.8.3",
+ "version": "8.8.4",
"license": "MIT",
"dependencies": {
- "@ionic/core": "^8.8.3"
+ "@ionic/core": "^8.8.4"
},
"devDependencies": {
"@angular-eslint/eslint-plugin": "^16.0.0",
@@ -11289,4 +11289,4 @@
}
}
}
-}
+}
\ No newline at end of file
diff --git a/packages/angular-server/package.json b/packages/angular-server/package.json
index 6e7325b33e3..2af2f292161 100644
--- a/packages/angular-server/package.json
+++ b/packages/angular-server/package.json
@@ -1,6 +1,6 @@
{
"name": "@ionic/angular-server",
- "version": "8.8.3",
+ "version": "8.8.4",
"description": "Angular SSR Module for Ionic",
"keywords": [
"ionic",
@@ -62,6 +62,6 @@
},
"prettier": "@ionic/prettier-config",
"dependencies": {
- "@ionic/core": "^8.8.3"
+ "@ionic/core": "^8.8.4"
}
}
diff --git a/packages/angular/CHANGELOG.md b/packages/angular/CHANGELOG.md
index 66721281c95..0c73bb9366f 100644
--- a/packages/angular/CHANGELOG.md
+++ b/packages/angular/CHANGELOG.md
@@ -3,6 +3,14 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
+## [8.8.4](https://github.com/ionic-team/ionic-framework/compare/v8.8.3...v8.8.4) (2026-04-15)
+
+**Note:** Version bump only for package @ionic/angular
+
+
+
+
+
## [8.8.3](https://github.com/ionic-team/ionic-framework/compare/v8.8.2...v8.8.3) (2026-04-01)
**Note:** Version bump only for package @ionic/angular
diff --git a/packages/angular/package-lock.json b/packages/angular/package-lock.json
index 90b33a3e6b3..b9c31ace5a7 100644
--- a/packages/angular/package-lock.json
+++ b/packages/angular/package-lock.json
@@ -1,15 +1,15 @@
{
"name": "@ionic/angular",
- "version": "8.8.3",
+ "version": "8.8.4",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@ionic/angular",
- "version": "8.8.3",
+ "version": "8.8.4",
"license": "MIT",
"dependencies": {
- "@ionic/core": "^8.8.3",
+ "@ionic/core": "^8.8.4",
"ionicons": "^8.0.13",
"jsonc-parser": "^3.0.0",
"tslib": "^2.3.0"
@@ -9095,4 +9095,4 @@
}
}
}
-}
+}
\ No newline at end of file
diff --git a/packages/angular/package.json b/packages/angular/package.json
index 2211afd24b3..3785f542d0a 100644
--- a/packages/angular/package.json
+++ b/packages/angular/package.json
@@ -1,6 +1,6 @@
{
"name": "@ionic/angular",
- "version": "8.8.3",
+ "version": "8.8.4",
"description": "Angular specific wrappers for @ionic/core",
"keywords": [
"ionic",
@@ -48,7 +48,7 @@
}
},
"dependencies": {
- "@ionic/core": "^8.8.3",
+ "@ionic/core": "^8.8.4",
"ionicons": "^8.0.13",
"jsonc-parser": "^3.0.0",
"tslib": "^2.3.0"
diff --git a/packages/docs/CHANGELOG.md b/packages/docs/CHANGELOG.md
index 588ab851d3d..2a483528e35 100644
--- a/packages/docs/CHANGELOG.md
+++ b/packages/docs/CHANGELOG.md
@@ -3,6 +3,14 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
+## [8.8.4](https://github.com/ionic-team/ionic-framework/compare/v8.8.3...v8.8.4) (2026-04-15)
+
+**Note:** Version bump only for package @ionic/docs
+
+
+
+
+
## [8.8.3](https://github.com/ionic-team/ionic-framework/compare/v8.8.2...v8.8.3) (2026-04-01)
**Note:** Version bump only for package @ionic/docs
diff --git a/packages/docs/package-lock.json b/packages/docs/package-lock.json
index 6e6bd26dc7a..158656a20f7 100644
--- a/packages/docs/package-lock.json
+++ b/packages/docs/package-lock.json
@@ -1,13 +1,13 @@
{
"name": "@ionic/docs",
- "version": "8.8.3",
+ "version": "8.8.4",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "@ionic/docs",
- "version": "8.8.3",
+ "version": "8.8.4",
"license": "MIT"
}
}
-}
+}
\ No newline at end of file
diff --git a/packages/docs/package.json b/packages/docs/package.json
index 91c1147613d..71eabacc599 100644
--- a/packages/docs/package.json
+++ b/packages/docs/package.json
@@ -1,6 +1,6 @@
{
"name": "@ionic/docs",
- "version": "8.8.3",
+ "version": "8.8.4",
"description": "Pre-packaged API documentation for the Ionic docs.",
"main": "core.json",
"types": "core.d.ts",
diff --git a/packages/react-router/CHANGELOG.md b/packages/react-router/CHANGELOG.md
index d7185dd9b1d..4354d774ac4 100644
--- a/packages/react-router/CHANGELOG.md
+++ b/packages/react-router/CHANGELOG.md
@@ -3,6 +3,14 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
+## [8.8.4](https://github.com/ionic-team/ionic-framework/compare/v8.8.3...v8.8.4) (2026-04-15)
+
+**Note:** Version bump only for package @ionic/react-router
+
+
+
+
+
## [8.8.3](https://github.com/ionic-team/ionic-framework/compare/v8.8.2...v8.8.3) (2026-04-01)
**Note:** Version bump only for package @ionic/react-router
diff --git a/packages/react-router/package-lock.json b/packages/react-router/package-lock.json
index dc82458d0e3..94f944650a9 100644
--- a/packages/react-router/package-lock.json
+++ b/packages/react-router/package-lock.json
@@ -1,15 +1,15 @@
{
"name": "@ionic/react-router",
- "version": "8.8.3",
+ "version": "8.8.4",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "@ionic/react-router",
- "version": "8.8.3",
+ "version": "8.8.4",
"license": "MIT",
"dependencies": {
- "@ionic/react": "^8.8.3",
+ "@ionic/react": "^8.8.4",
"tslib": "*"
},
"devDependencies": {
@@ -6847,4 +6847,4 @@
"dev": true
}
}
-}
+}
\ No newline at end of file
diff --git a/packages/react-router/package.json b/packages/react-router/package.json
index e77fef9f281..ed337f5b494 100644
--- a/packages/react-router/package.json
+++ b/packages/react-router/package.json
@@ -1,6 +1,6 @@
{
"name": "@ionic/react-router",
- "version": "8.8.3",
+ "version": "8.8.4",
"description": "React Router wrapper for @ionic/react",
"keywords": [
"ionic",
@@ -36,7 +36,7 @@
"dist/"
],
"dependencies": {
- "@ionic/react": "^8.8.3",
+ "@ionic/react": "^8.8.4",
"tslib": "*"
},
"peerDependencies": {
diff --git a/packages/react/CHANGELOG.md b/packages/react/CHANGELOG.md
index 0f8400c8511..dc8997adad5 100644
--- a/packages/react/CHANGELOG.md
+++ b/packages/react/CHANGELOG.md
@@ -3,6 +3,14 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
+## [8.8.4](https://github.com/ionic-team/ionic-framework/compare/v8.8.3...v8.8.4) (2026-04-15)
+
+**Note:** Version bump only for package @ionic/react
+
+
+
+
+
## [8.8.3](https://github.com/ionic-team/ionic-framework/compare/v8.8.2...v8.8.3) (2026-04-01)
**Note:** Version bump only for package @ionic/react
diff --git a/packages/react/package-lock.json b/packages/react/package-lock.json
index 3395ac5f3dc..821b953f957 100644
--- a/packages/react/package-lock.json
+++ b/packages/react/package-lock.json
@@ -1,15 +1,15 @@
{
"name": "@ionic/react",
- "version": "8.8.3",
+ "version": "8.8.4",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@ionic/react",
- "version": "8.8.3",
+ "version": "8.8.4",
"license": "MIT",
"dependencies": {
- "@ionic/core": "^8.8.3",
+ "@ionic/core": "^8.8.4",
"ionicons": "^8.0.13",
"tslib": "*"
},
@@ -11916,4 +11916,4 @@
}
}
}
-}
+}
\ No newline at end of file
diff --git a/packages/react/package.json b/packages/react/package.json
index a08514a5476..d7253110c7e 100644
--- a/packages/react/package.json
+++ b/packages/react/package.json
@@ -1,6 +1,6 @@
{
"name": "@ionic/react",
- "version": "8.8.3",
+ "version": "8.8.4",
"description": "React specific wrapper for @ionic/core",
"keywords": [
"ionic",
@@ -40,7 +40,7 @@
"css/"
],
"dependencies": {
- "@ionic/core": "^8.8.3",
+ "@ionic/core": "^8.8.4",
"ionicons": "^8.0.13",
"tslib": "*"
},
diff --git a/packages/vue-router/CHANGELOG.md b/packages/vue-router/CHANGELOG.md
index 00d1ab61982..ea8af11eccb 100644
--- a/packages/vue-router/CHANGELOG.md
+++ b/packages/vue-router/CHANGELOG.md
@@ -3,6 +3,14 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
+## [8.8.4](https://github.com/ionic-team/ionic-framework/compare/v8.8.3...v8.8.4) (2026-04-15)
+
+**Note:** Version bump only for package @ionic/vue-router
+
+
+
+
+
## [8.8.3](https://github.com/ionic-team/ionic-framework/compare/v8.8.2...v8.8.3) (2026-04-01)
**Note:** Version bump only for package @ionic/vue-router
diff --git a/packages/vue-router/package-lock.json b/packages/vue-router/package-lock.json
index 9c7b1d57853..05b1320c660 100644
--- a/packages/vue-router/package-lock.json
+++ b/packages/vue-router/package-lock.json
@@ -1,15 +1,15 @@
{
"name": "@ionic/vue-router",
- "version": "8.8.3",
+ "version": "8.8.4",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "@ionic/vue-router",
- "version": "8.8.3",
+ "version": "8.8.4",
"license": "MIT",
"dependencies": {
- "@ionic/vue": "^8.8.3"
+ "@ionic/vue": "^8.8.4"
},
"devDependencies": {
"@ionic/eslint-config": "^0.3.0",
@@ -12994,4 +12994,4 @@
"dev": true
}
}
-}
+}
\ No newline at end of file
diff --git a/packages/vue-router/package.json b/packages/vue-router/package.json
index 96fbba44ad2..b1ae2a0db94 100644
--- a/packages/vue-router/package.json
+++ b/packages/vue-router/package.json
@@ -1,6 +1,6 @@
{
"name": "@ionic/vue-router",
- "version": "8.8.3",
+ "version": "8.8.4",
"description": "Vue Router integration for @ionic/vue",
"scripts": {
"test.spec": "jest",
@@ -44,7 +44,7 @@
},
"homepage": "https://github.com/ionic-team/ionic-framework#readme",
"dependencies": {
- "@ionic/vue": "^8.8.3"
+ "@ionic/vue": "^8.8.4"
},
"devDependencies": {
"@ionic/eslint-config": "^0.3.0",
diff --git a/packages/vue/CHANGELOG.md b/packages/vue/CHANGELOG.md
index 425c7e49817..30c774c6c81 100644
--- a/packages/vue/CHANGELOG.md
+++ b/packages/vue/CHANGELOG.md
@@ -3,6 +3,14 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
+## [8.8.4](https://github.com/ionic-team/ionic-framework/compare/v8.8.3...v8.8.4) (2026-04-15)
+
+**Note:** Version bump only for package @ionic/vue
+
+
+
+
+
## [8.8.3](https://github.com/ionic-team/ionic-framework/compare/v8.8.2...v8.8.3) (2026-04-01)
**Note:** Version bump only for package @ionic/vue
diff --git a/packages/vue/package-lock.json b/packages/vue/package-lock.json
index a5c15bc6024..d1aba690bf3 100644
--- a/packages/vue/package-lock.json
+++ b/packages/vue/package-lock.json
@@ -1,15 +1,15 @@
{
"name": "@ionic/vue",
- "version": "8.8.3",
+ "version": "8.8.4",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@ionic/vue",
- "version": "8.8.3",
+ "version": "8.8.4",
"license": "MIT",
"dependencies": {
- "@ionic/core": "^8.8.3",
+ "@ionic/core": "^8.8.4",
"@stencil/vue-output-target": "0.10.7",
"ionicons": "^8.0.13"
},
@@ -4022,4 +4022,4 @@
"dev": true
}
}
-}
+}
\ No newline at end of file
diff --git a/packages/vue/package.json b/packages/vue/package.json
index 372305c4b78..277719e7c3b 100644
--- a/packages/vue/package.json
+++ b/packages/vue/package.json
@@ -1,6 +1,6 @@
{
"name": "@ionic/vue",
- "version": "8.8.3",
+ "version": "8.8.4",
"description": "Vue specific wrapper for @ionic/core",
"scripts": {
"eslint": "eslint src",
@@ -68,7 +68,7 @@
"vue-router": "^4.0.16"
},
"dependencies": {
- "@ionic/core": "^8.8.3",
+ "@ionic/core": "^8.8.4",
"@stencil/vue-output-target": "0.10.7",
"ionicons": "^8.0.13"
},
From 7053da1a5e803f9095c28e6f8c3c4215bd0aad20 Mon Sep 17 00:00:00 2001
From: ionitron
Date: Wed, 15 Apr 2026 18:13:11 +0000
Subject: [PATCH 03/11] chore(): update package lock files
---
core/package-lock.json | 2 +-
packages/angular-server/package-lock.json | 14 +++++------
packages/angular/package-lock.json | 8 +++---
packages/docs/package-lock.json | 2 +-
packages/react-router/package-lock.json | 30 +++++++++++------------
packages/react/package-lock.json | 8 +++---
packages/vue-router/package-lock.json | 30 +++++++++++------------
packages/vue/package-lock.json | 8 +++---
8 files changed, 51 insertions(+), 51 deletions(-)
diff --git a/core/package-lock.json b/core/package-lock.json
index 8bef7f2f1f7..007fed4a238 100644
--- a/core/package-lock.json
+++ b/core/package-lock.json
@@ -9829,4 +9829,4 @@
}
}
}
-}
\ No newline at end of file
+}
diff --git a/packages/angular-server/package-lock.json b/packages/angular-server/package-lock.json
index 62015e76228..798964509f8 100644
--- a/packages/angular-server/package-lock.json
+++ b/packages/angular-server/package-lock.json
@@ -1031,9 +1031,9 @@
"dev": true
},
"node_modules/@ionic/core": {
- "version": "8.8.3",
- "resolved": "https://registry.npmjs.org/@ionic/core/-/core-8.8.3.tgz",
- "integrity": "sha512-qvl+bRgZRvAJ35eW2iW0Vlo11T/EQsPazMU6z45QxJvcLukGJ59MwubjDTx6dPKteful4/FBzVt9etCvcNp8Gg==",
+ "version": "8.8.4",
+ "resolved": "https://registry.npmjs.org/@ionic/core/-/core-8.8.4.tgz",
+ "integrity": "sha512-RWts/72xtNNJDZQtntMRxB68KDctq76INV5OmLWWc0rlgcxOlNQDNH+lTBH7kV9vQ78JDVGSgi10ax3oQ3dIIQ==",
"license": "MIT",
"dependencies": {
"@stencil/core": "4.43.0",
@@ -7309,9 +7309,9 @@
"dev": true
},
"@ionic/core": {
- "version": "8.8.3",
- "resolved": "https://registry.npmjs.org/@ionic/core/-/core-8.8.3.tgz",
- "integrity": "sha512-qvl+bRgZRvAJ35eW2iW0Vlo11T/EQsPazMU6z45QxJvcLukGJ59MwubjDTx6dPKteful4/FBzVt9etCvcNp8Gg==",
+ "version": "8.8.4",
+ "resolved": "https://registry.npmjs.org/@ionic/core/-/core-8.8.4.tgz",
+ "integrity": "sha512-RWts/72xtNNJDZQtntMRxB68KDctq76INV5OmLWWc0rlgcxOlNQDNH+lTBH7kV9vQ78JDVGSgi10ax3oQ3dIIQ==",
"requires": {
"@stencil/core": "4.43.0",
"ionicons": "^8.0.13",
@@ -11289,4 +11289,4 @@
}
}
}
-}
\ No newline at end of file
+}
diff --git a/packages/angular/package-lock.json b/packages/angular/package-lock.json
index b9c31ace5a7..1178f97c7da 100644
--- a/packages/angular/package-lock.json
+++ b/packages/angular/package-lock.json
@@ -1398,9 +1398,9 @@
"dev": true
},
"node_modules/@ionic/core": {
- "version": "8.8.3",
- "resolved": "https://registry.npmjs.org/@ionic/core/-/core-8.8.3.tgz",
- "integrity": "sha512-qvl+bRgZRvAJ35eW2iW0Vlo11T/EQsPazMU6z45QxJvcLukGJ59MwubjDTx6dPKteful4/FBzVt9etCvcNp8Gg==",
+ "version": "8.8.4",
+ "resolved": "https://registry.npmjs.org/@ionic/core/-/core-8.8.4.tgz",
+ "integrity": "sha512-RWts/72xtNNJDZQtntMRxB68KDctq76INV5OmLWWc0rlgcxOlNQDNH+lTBH7kV9vQ78JDVGSgi10ax3oQ3dIIQ==",
"license": "MIT",
"dependencies": {
"@stencil/core": "4.43.0",
@@ -9095,4 +9095,4 @@
}
}
}
-}
\ No newline at end of file
+}
diff --git a/packages/docs/package-lock.json b/packages/docs/package-lock.json
index 158656a20f7..5110fe60ab6 100644
--- a/packages/docs/package-lock.json
+++ b/packages/docs/package-lock.json
@@ -10,4 +10,4 @@
"license": "MIT"
}
}
-}
\ No newline at end of file
+}
diff --git a/packages/react-router/package-lock.json b/packages/react-router/package-lock.json
index 94f944650a9..37b215ff62c 100644
--- a/packages/react-router/package-lock.json
+++ b/packages/react-router/package-lock.json
@@ -238,9 +238,9 @@
"dev": true
},
"node_modules/@ionic/core": {
- "version": "8.8.3",
- "resolved": "https://registry.npmjs.org/@ionic/core/-/core-8.8.3.tgz",
- "integrity": "sha512-qvl+bRgZRvAJ35eW2iW0Vlo11T/EQsPazMU6z45QxJvcLukGJ59MwubjDTx6dPKteful4/FBzVt9etCvcNp8Gg==",
+ "version": "8.8.4",
+ "resolved": "https://registry.npmjs.org/@ionic/core/-/core-8.8.4.tgz",
+ "integrity": "sha512-RWts/72xtNNJDZQtntMRxB68KDctq76INV5OmLWWc0rlgcxOlNQDNH+lTBH7kV9vQ78JDVGSgi10ax3oQ3dIIQ==",
"license": "MIT",
"dependencies": {
"@stencil/core": "4.43.0",
@@ -418,12 +418,12 @@
}
},
"node_modules/@ionic/react": {
- "version": "8.8.3",
- "resolved": "https://registry.npmjs.org/@ionic/react/-/react-8.8.3.tgz",
- "integrity": "sha512-mqUftIoYROKSiqD9rOyF0XwR1/5dsGpAW/JD5bcPJqbNKx2bopb0/uiddeQ3Vbhi37tz5nsxzX0NB1+3cdx7hQ==",
+ "version": "8.8.4",
+ "resolved": "https://registry.npmjs.org/@ionic/react/-/react-8.8.4.tgz",
+ "integrity": "sha512-qlQNV6sRVIsq+vs33COKfzvMbNKRpPBGWYATv6vsNWmX7OhOm9tk7QSZl91LECvZRHMIe6Sqn9xMB+Aw/IBRfA==",
"license": "MIT",
"dependencies": {
- "@ionic/core": "8.8.3",
+ "@ionic/core": "8.8.4",
"ionicons": "^8.0.13",
"tslib": "*"
},
@@ -4178,9 +4178,9 @@
"dev": true
},
"@ionic/core": {
- "version": "8.8.3",
- "resolved": "https://registry.npmjs.org/@ionic/core/-/core-8.8.3.tgz",
- "integrity": "sha512-qvl+bRgZRvAJ35eW2iW0Vlo11T/EQsPazMU6z45QxJvcLukGJ59MwubjDTx6dPKteful4/FBzVt9etCvcNp8Gg==",
+ "version": "8.8.4",
+ "resolved": "https://registry.npmjs.org/@ionic/core/-/core-8.8.4.tgz",
+ "integrity": "sha512-RWts/72xtNNJDZQtntMRxB68KDctq76INV5OmLWWc0rlgcxOlNQDNH+lTBH7kV9vQ78JDVGSgi10ax3oQ3dIIQ==",
"requires": {
"@stencil/core": "4.43.0",
"ionicons": "^8.0.13",
@@ -4284,11 +4284,11 @@
"requires": {}
},
"@ionic/react": {
- "version": "8.8.3",
- "resolved": "https://registry.npmjs.org/@ionic/react/-/react-8.8.3.tgz",
- "integrity": "sha512-mqUftIoYROKSiqD9rOyF0XwR1/5dsGpAW/JD5bcPJqbNKx2bopb0/uiddeQ3Vbhi37tz5nsxzX0NB1+3cdx7hQ==",
+ "version": "8.8.4",
+ "resolved": "https://registry.npmjs.org/@ionic/react/-/react-8.8.4.tgz",
+ "integrity": "sha512-qlQNV6sRVIsq+vs33COKfzvMbNKRpPBGWYATv6vsNWmX7OhOm9tk7QSZl91LECvZRHMIe6Sqn9xMB+Aw/IBRfA==",
"requires": {
- "@ionic/core": "8.8.3",
+ "@ionic/core": "8.8.4",
"ionicons": "^8.0.13",
"tslib": "*"
}
@@ -6847,4 +6847,4 @@
"dev": true
}
}
-}
\ No newline at end of file
+}
diff --git a/packages/react/package-lock.json b/packages/react/package-lock.json
index 821b953f957..487b165f5b4 100644
--- a/packages/react/package-lock.json
+++ b/packages/react/package-lock.json
@@ -736,9 +736,9 @@
"dev": true
},
"node_modules/@ionic/core": {
- "version": "8.8.3",
- "resolved": "https://registry.npmjs.org/@ionic/core/-/core-8.8.3.tgz",
- "integrity": "sha512-qvl+bRgZRvAJ35eW2iW0Vlo11T/EQsPazMU6z45QxJvcLukGJ59MwubjDTx6dPKteful4/FBzVt9etCvcNp8Gg==",
+ "version": "8.8.4",
+ "resolved": "https://registry.npmjs.org/@ionic/core/-/core-8.8.4.tgz",
+ "integrity": "sha512-RWts/72xtNNJDZQtntMRxB68KDctq76INV5OmLWWc0rlgcxOlNQDNH+lTBH7kV9vQ78JDVGSgi10ax3oQ3dIIQ==",
"license": "MIT",
"dependencies": {
"@stencil/core": "4.43.0",
@@ -11916,4 +11916,4 @@
}
}
}
-}
\ No newline at end of file
+}
diff --git a/packages/vue-router/package-lock.json b/packages/vue-router/package-lock.json
index 05b1320c660..0dffc9612f6 100644
--- a/packages/vue-router/package-lock.json
+++ b/packages/vue-router/package-lock.json
@@ -673,9 +673,9 @@
"dev": true
},
"node_modules/@ionic/core": {
- "version": "8.8.3",
- "resolved": "https://registry.npmjs.org/@ionic/core/-/core-8.8.3.tgz",
- "integrity": "sha512-qvl+bRgZRvAJ35eW2iW0Vlo11T/EQsPazMU6z45QxJvcLukGJ59MwubjDTx6dPKteful4/FBzVt9etCvcNp8Gg==",
+ "version": "8.8.4",
+ "resolved": "https://registry.npmjs.org/@ionic/core/-/core-8.8.4.tgz",
+ "integrity": "sha512-RWts/72xtNNJDZQtntMRxB68KDctq76INV5OmLWWc0rlgcxOlNQDNH+lTBH7kV9vQ78JDVGSgi10ax3oQ3dIIQ==",
"license": "MIT",
"dependencies": {
"@stencil/core": "4.43.0",
@@ -868,12 +868,12 @@
}
},
"node_modules/@ionic/vue": {
- "version": "8.8.3",
- "resolved": "https://registry.npmjs.org/@ionic/vue/-/vue-8.8.3.tgz",
- "integrity": "sha512-6bwi2RodoxECt2Ef6C6km4ZkuH4TweyhF800/XpunOKjM2Iya3pD1j230KSRd35cJpAenGc1uplLdIZhAyMsWg==",
+ "version": "8.8.4",
+ "resolved": "https://registry.npmjs.org/@ionic/vue/-/vue-8.8.4.tgz",
+ "integrity": "sha512-FZftTMul3zw7vjZHAxznheSu0sy0h6GSLtKNeU5U4XtziOaMYZ9dLP5TWsBjRVahAEtqn+n6493fYW4EPE+dvw==",
"license": "MIT",
"dependencies": {
- "@ionic/core": "8.8.3",
+ "@ionic/core": "8.8.4",
"@stencil/vue-output-target": "0.10.7",
"ionicons": "^8.0.13"
}
@@ -8044,9 +8044,9 @@
"dev": true
},
"@ionic/core": {
- "version": "8.8.3",
- "resolved": "https://registry.npmjs.org/@ionic/core/-/core-8.8.3.tgz",
- "integrity": "sha512-qvl+bRgZRvAJ35eW2iW0Vlo11T/EQsPazMU6z45QxJvcLukGJ59MwubjDTx6dPKteful4/FBzVt9etCvcNp8Gg==",
+ "version": "8.8.4",
+ "resolved": "https://registry.npmjs.org/@ionic/core/-/core-8.8.4.tgz",
+ "integrity": "sha512-RWts/72xtNNJDZQtntMRxB68KDctq76INV5OmLWWc0rlgcxOlNQDNH+lTBH7kV9vQ78JDVGSgi10ax3oQ3dIIQ==",
"requires": {
"@stencil/core": "4.43.0",
"ionicons": "^8.0.13",
@@ -8159,11 +8159,11 @@
"requires": {}
},
"@ionic/vue": {
- "version": "8.8.3",
- "resolved": "https://registry.npmjs.org/@ionic/vue/-/vue-8.8.3.tgz",
- "integrity": "sha512-6bwi2RodoxECt2Ef6C6km4ZkuH4TweyhF800/XpunOKjM2Iya3pD1j230KSRd35cJpAenGc1uplLdIZhAyMsWg==",
+ "version": "8.8.4",
+ "resolved": "https://registry.npmjs.org/@ionic/vue/-/vue-8.8.4.tgz",
+ "integrity": "sha512-FZftTMul3zw7vjZHAxznheSu0sy0h6GSLtKNeU5U4XtziOaMYZ9dLP5TWsBjRVahAEtqn+n6493fYW4EPE+dvw==",
"requires": {
- "@ionic/core": "8.8.3",
+ "@ionic/core": "8.8.4",
"@stencil/vue-output-target": "0.10.7",
"ionicons": "^8.0.13"
}
@@ -12994,4 +12994,4 @@
"dev": true
}
}
-}
\ No newline at end of file
+}
diff --git a/packages/vue/package-lock.json b/packages/vue/package-lock.json
index d1aba690bf3..b21f4c5a4ba 100644
--- a/packages/vue/package-lock.json
+++ b/packages/vue/package-lock.json
@@ -222,9 +222,9 @@
"dev": true
},
"node_modules/@ionic/core": {
- "version": "8.8.3",
- "resolved": "https://registry.npmjs.org/@ionic/core/-/core-8.8.3.tgz",
- "integrity": "sha512-qvl+bRgZRvAJ35eW2iW0Vlo11T/EQsPazMU6z45QxJvcLukGJ59MwubjDTx6dPKteful4/FBzVt9etCvcNp8Gg==",
+ "version": "8.8.4",
+ "resolved": "https://registry.npmjs.org/@ionic/core/-/core-8.8.4.tgz",
+ "integrity": "sha512-RWts/72xtNNJDZQtntMRxB68KDctq76INV5OmLWWc0rlgcxOlNQDNH+lTBH7kV9vQ78JDVGSgi10ax3oQ3dIIQ==",
"license": "MIT",
"dependencies": {
"@stencil/core": "4.43.0",
@@ -4022,4 +4022,4 @@
"dev": true
}
}
-}
\ No newline at end of file
+}
From 12800cabfd22f785fcadb892fbbc91b5eff92dcd Mon Sep 17 00:00:00 2001
From: Maria Hutt
Date: Wed, 15 Apr 2026 13:30:10 -0700
Subject: [PATCH 04/11] test(datetime): skip failing test
---
core/src/components/datetime/test/basic/datetime.e2e.ts | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/core/src/components/datetime/test/basic/datetime.e2e.ts b/core/src/components/datetime/test/basic/datetime.e2e.ts
index bbc6033374f..4865e243092 100644
--- a/core/src/components/datetime/test/basic/datetime.e2e.ts
+++ b/core/src/components/datetime/test/basic/datetime.e2e.ts
@@ -440,7 +440,10 @@ configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) =>
*/
configs({ modes: ['md'], directions: ['ltr'] }).forEach(({ title, config }) => {
test.describe(title('datetime: IO fallback'), () => {
- test('should become ready even if IntersectionObserver never reports visible', async ({ page }, testInfo) => {
+ test('should become ready even if IntersectionObserver never reports visible', async ({ page, skip }, testInfo) => {
+ // TODO(FW-7284): Re-enable on WebKit after determining why it fails
+ skip.browser('webkit', 'Wheel is not available in WebKit');
+
testInfo.annotations.push({
type: 'issue',
description: 'https://github.com/ionic-team/ionic-framework/issues/30706',
From 0c178ef7b3513744a461bbabd8dbcfcae008ba3f Mon Sep 17 00:00:00 2001
From: Shane
Date: Thu, 16 Apr 2026 08:32:36 -0700
Subject: [PATCH 05/11] chore(build): add Vercel preview builds for framework
test apps (#31073)
Issue number: resolves internal
---------
## What is the current behavior?
Vercel preview deployments only build and serve core Stencil component
test pages. Framework-specific test apps (Angular, React, Vue, React
Router) are not included, requiring developers to pull and build locally
to validate them.
## What is the new behavior?
Vercel preview deployments build and serve framework test apps alongside
core component tests under a single preview URL.
## Does this introduce a breaking change?
- [ ] Yes
- [X] No
## Other information
---
core/scripts/vercel-build.sh | 354 ++++++++++++++++++
core/vercel.json | 12 +
packages/react/test/base/src/App.tsx | 3 +-
.../react/test/base/src/react-app-env.d.ts | 8 +
4 files changed, 376 insertions(+), 1 deletion(-)
create mode 100755 core/scripts/vercel-build.sh
create mode 100644 core/vercel.json
diff --git a/core/scripts/vercel-build.sh b/core/scripts/vercel-build.sh
new file mode 100755
index 00000000000..daf44142dce
--- /dev/null
+++ b/core/scripts/vercel-build.sh
@@ -0,0 +1,354 @@
+#!/bin/bash
+#
+# Vercel preview build script
+#
+# Builds core component tests (same as before) plus framework test apps
+# (Angular, React, Vue) so they're all accessible from a single preview URL.
+#
+# Core tests: /src/components/{name}/test/{scenario}
+# Angular test app: /angular/
+# React test app: /react/
+# Vue test app: /vue/
+#
+set -e
+
+# Vercel places core/ at /vercel/path1 (bind mount). The full repo clone
+# lives at /vercel/path0. We can't rely on `..` to reach it, so we search.
+CORE_DIR=$(pwd)
+OUTPUT_DIR="${CORE_DIR}/../_vercel_output"
+
+# Find the actual repo root (the directory containing packages/)
+REPO_ROOT=""
+for candidate in /vercel/path0 /vercel/path1 "${CORE_DIR}/.."; do
+ if [ -d "${candidate}/packages" ]; then
+ REPO_ROOT="${candidate}"
+ break
+ fi
+done
+
+echo "=== Ionic Framework Preview Build ==="
+echo "Core dir: ${CORE_DIR}"
+echo "Repo root: ${REPO_ROOT:-NOT FOUND}"
+if [ -z "${REPO_ROOT}" ]; then
+ echo "(This is expected in some Vercel configs -- framework test apps will be skipped)"
+fi
+
+rm -rf "${OUTPUT_DIR}"
+mkdir -p "${OUTPUT_DIR}"
+
+# Step 1 - Build Core (dependencies already installed by Vercel installCommand)
+echo ""
+echo "--- Step 1: Building Core ---"
+npm run build
+
+# Copy core files to output. The test HTML files use relative paths like
+# ../../../../../dist/ionic/ionic.esm.js so the directory structure must
+# be preserved exactly.
+echo "Copying core output..."
+cp -r "${CORE_DIR}/src" "${OUTPUT_DIR}/src"
+cp -r "${CORE_DIR}/dist" "${OUTPUT_DIR}/dist"
+cp -r "${CORE_DIR}/css" "${OUTPUT_DIR}/css"
+mkdir -p "${OUTPUT_DIR}/scripts"
+cp -r "${CORE_DIR}/scripts/testing" "${OUTPUT_DIR}/scripts/testing"
+
+# Generate directory index pages so users can browse core test pages.
+# Creates an index.html in every directory under src/components/ that
+# doesn't already have one. Only includes child directories that eventually
+# lead to a test page (an index.html). Prunes snapshot dirs and dead ends.
+echo "Generating directory indexes for core tests..."
+generate_dir_index() {
+ local dir="$1"
+ local url_path="$2"
+ # Skip if an index.html already exists (it's an actual test page)
+ [ -f "${dir}/index.html" ] && return
+
+ local entries=""
+ for child in "${dir}"/*/; do
+ [ -d "${child}" ] || continue
+ local name=$(basename "${child}")
+ # Skip snapshot directories and hidden dirs
+ case "${name}" in *-snapshots|.*) continue ;; esac
+ # Only include if there's at least one index.html somewhere underneath
+ find "${child}" -name "index.html" -print -quit | grep -q . || continue
+ entries="${entries}${name}/\n"
+ done
+
+ [ -z "${entries}" ] && return
+
+ cat > "${dir}/index.html" << IDXEOF
+
+
+
+
+
+ Index of ${url_path}
+
+
+
+
Index of ${url_path}
+ ../
+$(echo -e "${entries}")
+
+
+IDXEOF
+}
+
+# Walk all directories under src/ (bottom-up so parent indexes reflect pruned children)
+find "${OUTPUT_DIR}/src" -depth -type d | while IFS= read -r dir; do
+ url_path="${dir#${OUTPUT_DIR}}"
+ generate_dir_index "${dir}" "${url_path}/"
+done
+
+# Vercel mounts core/ at a separate path (path1) from the repo clone (path0).
+# Framework packages reference core via relative paths (../../core/css etc.),
+# which resolve to path0/core/ -- not path1/ where we just built.
+# Symlink path0/core -> path1 so those references find the build outputs.
+if [ -n "${REPO_ROOT}" ] && [ "${CORE_DIR}" != "${REPO_ROOT}/core" ] && [ -d "${REPO_ROOT}/core" ]; then
+ echo "Linking ${REPO_ROOT}/core -> ${CORE_DIR} (so framework builds find core outputs)"
+ rm -rf "${REPO_ROOT}/core"
+ ln -s "${CORE_DIR}" "${REPO_ROOT}/core"
+fi
+
+# Check if the full repo is available
+if [ -z "${REPO_ROOT}" ]; then
+ echo ""
+ echo "WARNING: Could not find repo root (no directory with packages/ found)"
+ echo "Only core tests will be deployed (framework test apps require the full repo)."
+
+ # Generate landing page and exit -- core tests are still useful
+ cat > "${OUTPUT_DIR}/index.html" << 'LANDING_EOF'
+
+Ionic Framework - Preview
+
Ionic Framework Preview
Core tests only. Browse to /src/components/{name}/test/{scenario}/
+
+LANDING_EOF
+
+ echo "=== Preview build complete (core only) ==="
+ exit 0
+fi
+
+# Step 2 - Build Framework Packages (parallel)
+echo ""
+echo "--- Step 2: Building Framework Packages ---"
+
+build_angular_pkgs() {
+ (cd "${REPO_ROOT}/packages/angular" && npm install && npm run sync && npm run build) || return 1
+ (cd "${REPO_ROOT}/packages/angular-server" && npm install && npm run build) || return 1
+}
+
+build_react_pkgs() {
+ (cd "${REPO_ROOT}/packages/react" && npm install && npm run sync && npm run build) || return 1
+ (cd "${REPO_ROOT}/packages/react-router" && npm install && npm run build) || return 1
+}
+
+build_vue_pkgs() {
+ (cd "${REPO_ROOT}/packages/vue" && npm install && npm run sync && npm run build) || return 1
+ (cd "${REPO_ROOT}/packages/vue-router" && npm install && npm run build) || return 1
+}
+
+build_angular_pkgs > /tmp/vercel-angular-pkg.log 2>&1 &
+PID_ANG=$!
+build_react_pkgs > /tmp/vercel-react-pkg.log 2>&1 &
+PID_REACT=$!
+build_vue_pkgs > /tmp/vercel-vue-pkg.log 2>&1 &
+PID_VUE=$!
+
+ANG_PKG_OK=true; REACT_PKG_OK=true; VUE_PKG_OK=true
+wait $PID_ANG || { echo "Angular packages failed:"; tail -30 /tmp/vercel-angular-pkg.log; ANG_PKG_OK=false; }
+wait $PID_REACT || { echo "React packages failed:"; tail -30 /tmp/vercel-react-pkg.log; REACT_PKG_OK=false; }
+wait $PID_VUE || { echo "Vue packages failed:"; tail -30 /tmp/vercel-vue-pkg.log; VUE_PKG_OK=false; }
+
+if ! $ANG_PKG_OK || ! $REACT_PKG_OK || ! $VUE_PKG_OK; then
+ echo "ERROR: Some framework package builds failed."
+ echo "Core tests will still be deployed. Skipping failed framework test apps."
+else
+ echo "All framework packages built."
+fi
+
+# Step 3 - Build Framework Test Apps (parallel)
+echo ""
+echo "--- Step 3: Building Framework Test Apps ---"
+
+# Find the best available app version for a given package.
+# Scans the apps/ directory and picks the newest version (reverse version sort).
+pick_app() {
+ local apps_dir="$1/apps"
+ [ -d "${apps_dir}" ] || return 1
+ local app
+ app=$(ls -1d "${apps_dir}"/*/ 2>/dev/null | xargs -n1 basename | sort -V -r | head -1)
+ [ -n "${app}" ] && echo "${app}" && return 0
+ return 1
+}
+
+build_angular_test() {
+ local APP
+ APP=$(pick_app "${REPO_ROOT}/packages/angular/test") || {
+ echo "[angular] No test app found, skipping."
+ return 0
+ }
+ echo "[angular] Building ${APP}..."
+
+ cd "${REPO_ROOT}/packages/angular/test"
+ ./build.sh "${APP}"
+ cd "build/${APP}"
+ npm install
+ npm run sync
+ # --base-href sets so Angular Router works under the sub-path
+ npm run build -- --base-href /angular/
+
+ # Output path assumes the 'browser' builder. If migrated to 'application' builder, update this.
+ if [ ! -d "dist/test-app/browser" ]; then
+ echo "[angular] ERROR: Expected output at dist/test-app/browser/ not found."
+ return 1
+ fi
+ mkdir -p "${OUTPUT_DIR}/angular"
+ cp -r dist/test-app/browser/* "${OUTPUT_DIR}/angular/"
+ echo "[angular] Done."
+}
+
+build_react_test() {
+ local APP
+ APP=$(pick_app "${REPO_ROOT}/packages/react/test") || {
+ echo "[react] No test app found, skipping."
+ return 0
+ }
+ echo "[react] Building ${APP}..."
+
+ cd "${REPO_ROOT}/packages/react/test"
+ ./build.sh "${APP}"
+ cd "build/${APP}"
+ npm install
+ npm run sync
+ # --base sets Vite's base URL; import.meta.env.BASE_URL is read by IonReactRouter basename
+ npx vite build --base /react/
+
+ mkdir -p "${OUTPUT_DIR}/react"
+ cp -r dist/* "${OUTPUT_DIR}/react/"
+ echo "[react] Done."
+}
+
+build_vue_test() {
+ local APP
+ APP=$(pick_app "${REPO_ROOT}/packages/vue/test") || {
+ echo "[vue] No test app found, skipping."
+ return 0
+ }
+ echo "[vue] Building ${APP}..."
+
+ cd "${REPO_ROOT}/packages/vue/test"
+ ./build.sh "${APP}"
+ cd "build/${APP}"
+ npm install
+ npm run sync
+ # Vue Router already reads import.meta.env.BASE_URL which Vite sets from --base
+ npx vite build --base /vue/
+
+ mkdir -p "${OUTPUT_DIR}/vue"
+ cp -r dist/* "${OUTPUT_DIR}/vue/"
+ echo "[vue] Done."
+}
+
+# TODO: Add build_react_router_test() when reactrouter6-* apps are added to
+# packages/react-router/test/apps/
+
+TEST_FAILED=""
+
+if $ANG_PKG_OK; then
+ build_angular_test > /tmp/vercel-angular-test.log 2>&1 &
+ PID_ANG_TEST=$!
+fi
+if $REACT_PKG_OK; then
+ build_react_test > /tmp/vercel-react-test.log 2>&1 &
+ PID_REACT_TEST=$!
+fi
+if $VUE_PKG_OK; then
+ build_vue_test > /tmp/vercel-vue-test.log 2>&1 &
+ PID_VUE_TEST=$!
+fi
+
+if $ANG_PKG_OK; then
+ wait $PID_ANG_TEST || { echo "Angular test app failed:"; tail -30 /tmp/vercel-angular-test.log; TEST_FAILED="${TEST_FAILED} angular"; }
+fi
+if $REACT_PKG_OK; then
+ wait $PID_REACT_TEST || { echo "React test app failed:"; tail -30 /tmp/vercel-react-test.log; TEST_FAILED="${TEST_FAILED} react"; }
+fi
+if $VUE_PKG_OK; then
+ wait $PID_VUE_TEST || { echo "Vue test app failed:"; tail -30 /tmp/vercel-vue-test.log; TEST_FAILED="${TEST_FAILED} vue"; }
+fi
+
+if [ -n "${TEST_FAILED}" ]; then
+ echo ""
+ echo "WARNING: Some test app builds failed:${TEST_FAILED}"
+ echo "Core tests and successful framework apps will still be deployed."
+fi
+
+# Step 4 - Landing Page
+echo ""
+echo "--- Step 4: Generating landing page ---"
+
+cat > "${OUTPUT_DIR}/index.html" << 'LANDING_EOF'
+
+
+
+
+
+ Ionic Framework - Preview
+
+
+
+