diff --git a/.github/actions/publish-npm/action.yml b/.github/actions/publish-npm/action.yml
index 132b57f75b3..d3e06d5fb01 100644
--- a/.github/actions/publish-npm/action.yml
+++ b/.github/actions/publish-npm/action.yml
@@ -22,7 +22,7 @@ runs:
using: 'composite'
steps:
- name: 🟢 Configure Node for Publish
- uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
+ uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version: ${{ inputs.node-version }}
registry-url: 'https://registry.npmjs.org'
diff --git a/.github/workflows/actions/build-angular-server/action.yml b/.github/workflows/actions/build-angular-server/action.yml
index 3cab52b650a..b5d37c5a9ac 100644
--- a/.github/workflows/actions/build-angular-server/action.yml
+++ b/.github/workflows/actions/build-angular-server/action.yml
@@ -3,7 +3,7 @@ description: 'Build Ionic Angular Server'
runs:
using: 'composite'
steps:
- - uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
+ - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version: 24.x
- uses: ./.github/workflows/actions/download-archive
diff --git a/.github/workflows/actions/build-core-stencil-prerelease/action.yml b/.github/workflows/actions/build-core-stencil-prerelease/action.yml
index 913e8f494ff..e23d9119831 100644
--- a/.github/workflows/actions/build-core-stencil-prerelease/action.yml
+++ b/.github/workflows/actions/build-core-stencil-prerelease/action.yml
@@ -9,7 +9,7 @@ runs:
using: 'composite'
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- - uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
+ - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version: 24.x
diff --git a/.github/workflows/actions/build-core/action.yml b/.github/workflows/actions/build-core/action.yml
index 2b5117cf7af..7524c8a97b3 100644
--- a/.github/workflows/actions/build-core/action.yml
+++ b/.github/workflows/actions/build-core/action.yml
@@ -9,7 +9,7 @@ runs:
using: 'composite'
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- - uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
+ - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version: 24.x
- name: 🕸️ Install Dependencies
diff --git a/.github/workflows/actions/build-react-router/action.yml b/.github/workflows/actions/build-react-router/action.yml
index 568c835c42f..c8083494b0a 100644
--- a/.github/workflows/actions/build-react-router/action.yml
+++ b/.github/workflows/actions/build-react-router/action.yml
@@ -3,7 +3,7 @@ description: 'Build Ionic React Router'
runs:
using: 'composite'
steps:
- - uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
+ - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version: 24.x
- uses: ./.github/workflows/actions/download-archive
diff --git a/.github/workflows/actions/build-react/action.yml b/.github/workflows/actions/build-react/action.yml
index 9b4a5995e9e..5899335ad3e 100644
--- a/.github/workflows/actions/build-react/action.yml
+++ b/.github/workflows/actions/build-react/action.yml
@@ -3,7 +3,7 @@ description: 'Build Ionic React'
runs:
using: 'composite'
steps:
- - uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
+ - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version: 24.x
- uses: ./.github/workflows/actions/download-archive
diff --git a/.github/workflows/actions/build-vue-router/action.yml b/.github/workflows/actions/build-vue-router/action.yml
index efd4579f565..9b07ce64973 100644
--- a/.github/workflows/actions/build-vue-router/action.yml
+++ b/.github/workflows/actions/build-vue-router/action.yml
@@ -3,7 +3,7 @@ description: 'Builds Ionic Vue Router'
runs:
using: 'composite'
steps:
- - uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
+ - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version: 24.x
- uses: ./.github/workflows/actions/download-archive
diff --git a/.github/workflows/actions/build-vue/action.yml b/.github/workflows/actions/build-vue/action.yml
index 170e889f968..5c7497ec359 100644
--- a/.github/workflows/actions/build-vue/action.yml
+++ b/.github/workflows/actions/build-vue/action.yml
@@ -3,7 +3,7 @@ description: 'Build Ionic Vue'
runs:
using: 'composite'
steps:
- - uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
+ - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version: 24.x
- uses: ./.github/workflows/actions/download-archive
diff --git a/.github/workflows/actions/test-angular-e2e/action.yml b/.github/workflows/actions/test-angular-e2e/action.yml
index 11aa8eb789c..a4835a0210a 100644
--- a/.github/workflows/actions/test-angular-e2e/action.yml
+++ b/.github/workflows/actions/test-angular-e2e/action.yml
@@ -6,7 +6,7 @@ inputs:
runs:
using: 'composite'
steps:
- - uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
+ - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version: 24.x
- uses: ./.github/workflows/actions/download-archive
diff --git a/.github/workflows/actions/test-core-clean-build/action.yml b/.github/workflows/actions/test-core-clean-build/action.yml
index 92e3fed394b..96abc90121c 100644
--- a/.github/workflows/actions/test-core-clean-build/action.yml
+++ b/.github/workflows/actions/test-core-clean-build/action.yml
@@ -3,7 +3,7 @@ description: 'Test Core Clean Build'
runs:
using: 'composite'
steps:
- - uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
+ - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version: 24.x
diff --git a/.github/workflows/actions/test-core-lint/action.yml b/.github/workflows/actions/test-core-lint/action.yml
index 321a2d26304..f9f0011719a 100644
--- a/.github/workflows/actions/test-core-lint/action.yml
+++ b/.github/workflows/actions/test-core-lint/action.yml
@@ -3,7 +3,7 @@ description: 'Test Core Lint'
runs:
using: 'composite'
steps:
- - uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
+ - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version: 24.x
- name: 🕸️ Install Dependencies
diff --git a/.github/workflows/actions/test-core-screenshot/action.yml b/.github/workflows/actions/test-core-screenshot/action.yml
index 7ffa40faf5c..1f8699e66d4 100644
--- a/.github/workflows/actions/test-core-screenshot/action.yml
+++ b/.github/workflows/actions/test-core-screenshot/action.yml
@@ -13,7 +13,7 @@ inputs:
runs:
using: 'composite'
steps:
- - uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
+ - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version: 24.x
- uses: ./.github/workflows/actions/download-archive
diff --git a/.github/workflows/actions/test-core-spec/action.yml b/.github/workflows/actions/test-core-spec/action.yml
index f25207f6a49..2aab4b1be94 100644
--- a/.github/workflows/actions/test-core-spec/action.yml
+++ b/.github/workflows/actions/test-core-spec/action.yml
@@ -6,7 +6,7 @@ inputs:
runs:
using: 'composite'
steps:
- - uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
+ - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version: 24.x
- name: 🕸️ Install Dependencies
diff --git a/.github/workflows/actions/test-react-e2e/action.yml b/.github/workflows/actions/test-react-e2e/action.yml
index a6f1d42ba72..a1bcbf7a4db 100644
--- a/.github/workflows/actions/test-react-e2e/action.yml
+++ b/.github/workflows/actions/test-react-e2e/action.yml
@@ -6,7 +6,7 @@ inputs:
runs:
using: 'composite'
steps:
- - uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
+ - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version: 24.x
- uses: ./.github/workflows/actions/download-archive
diff --git a/.github/workflows/actions/test-react-router-e2e/action.yml b/.github/workflows/actions/test-react-router-e2e/action.yml
index 70dff8db874..034cfdce747 100644
--- a/.github/workflows/actions/test-react-router-e2e/action.yml
+++ b/.github/workflows/actions/test-react-router-e2e/action.yml
@@ -6,7 +6,7 @@ inputs:
runs:
using: 'composite'
steps:
- - uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
+ - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version: 24.x
- uses: ./.github/workflows/actions/download-archive
diff --git a/.github/workflows/actions/test-vue-e2e/action.yml b/.github/workflows/actions/test-vue-e2e/action.yml
index 060e923bdf4..191cd193c8a 100644
--- a/.github/workflows/actions/test-vue-e2e/action.yml
+++ b/.github/workflows/actions/test-vue-e2e/action.yml
@@ -6,7 +6,7 @@ inputs:
runs:
using: 'composite'
steps:
- - uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
+ - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version: 24.x
- uses: ./.github/workflows/actions/download-archive
diff --git a/.github/workflows/actions/update-reference-screenshots/action.yml b/.github/workflows/actions/update-reference-screenshots/action.yml
index 51d7bdce508..6ee56689b10 100644
--- a/.github/workflows/actions/update-reference-screenshots/action.yml
+++ b/.github/workflows/actions/update-reference-screenshots/action.yml
@@ -7,7 +7,7 @@ on:
runs:
using: 'composite'
steps:
- - uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
+ - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version: 24.x
- uses: actions/download-artifact@v8
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 84abbbee409..64a717080be 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -3,6 +3,18 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
+## [8.8.5](https://github.com/ionic-team/ionic-framework/compare/v8.8.4...v8.8.5) (2026-04-29)
+
+
+### Bug Fixes
+
+* **modal:** remove safe-area gap and flash in fullscreen modals ([#31092](https://github.com/ionic-team/ionic-framework/issues/31092)) ([f3cd39b](https://github.com/ionic-team/ionic-framework/commit/f3cd39b7fb291286374285c4a326ec6b9a8ea237)), closes [#31015](https://github.com/ionic-team/ionic-framework/issues/31015)
+* **select:** select focused option on Enter in popover and modal interfaces ([#31093](https://github.com/ionic-team/ionic-framework/issues/31093)) ([fd79771](https://github.com/ionic-team/ionic-framework/commit/fd79771e5be77c9f38379a3a7b9ab44bb11ff325)), closes [#30561](https://github.com/ionic-team/ionic-framework/issues/30561)
+
+
+
+
+
## [8.8.4](https://github.com/ionic-team/ionic-framework/compare/v8.8.3...v8.8.4) (2026-04-15)
diff --git a/core/CHANGELOG.md b/core/CHANGELOG.md
index ee45ffd629a..851601359f5 100644
--- a/core/CHANGELOG.md
+++ b/core/CHANGELOG.md
@@ -3,6 +3,18 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
+## [8.8.5](https://github.com/ionic-team/ionic-framework/compare/v8.8.4...v8.8.5) (2026-04-29)
+
+
+### Bug Fixes
+
+* **modal:** remove safe-area gap and flash in fullscreen modals ([#31092](https://github.com/ionic-team/ionic-framework/issues/31092)) ([f3cd39b](https://github.com/ionic-team/ionic-framework/commit/f3cd39b7fb291286374285c4a326ec6b9a8ea237)), closes [#31015](https://github.com/ionic-team/ionic-framework/issues/31015)
+* **select:** select focused option on Enter in popover and modal interfaces ([#31093](https://github.com/ionic-team/ionic-framework/issues/31093)) ([fd79771](https://github.com/ionic-team/ionic-framework/commit/fd79771e5be77c9f38379a3a7b9ab44bb11ff325)), closes [#30561](https://github.com/ionic-team/ionic-framework/issues/30561)
+
+
+
+
+
## [8.8.4](https://github.com/ionic-team/ionic-framework/compare/v8.8.3...v8.8.4) (2026-04-15)
diff --git a/core/package-lock.json b/core/package-lock.json
index 007fed4a238..9f0ef9cadb1 100644
--- a/core/package-lock.json
+++ b/core/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "@ionic/core",
- "version": "8.8.4",
+ "version": "8.8.5",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@ionic/core",
- "version": "8.8.4",
+ "version": "8.8.5",
"license": "MIT",
"dependencies": {
"@stencil/core": "4.43.0",
@@ -629,9 +629,9 @@
"license": "MIT"
},
"node_modules/@capacitor/core": {
- "version": "8.3.0",
- "resolved": "https://registry.npmjs.org/@capacitor/core/-/core-8.3.0.tgz",
- "integrity": "sha512-S4ajn4G/fS3VJj8salxqH/3LO5PPWv1VxGKQ27OCajnDcLJjEg9VXwgMPnlypgkIOqCJ2fmQLtk8GT+BlI9/rw==",
+ "version": "8.3.1",
+ "resolved": "https://registry.npmjs.org/@capacitor/core/-/core-8.3.1.tgz",
+ "integrity": "sha512-UF8ItlHguU1Z6GXfPTeT2gakf+ctNI8pAS1kwSBQlsJMlfD4OPoto/SmKnOxKCQvnF4WRcdWeg6C0zREUNaAQg==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -649,9 +649,9 @@
}
},
"node_modules/@capacitor/keyboard": {
- "version": "8.0.2",
- "resolved": "https://registry.npmjs.org/@capacitor/keyboard/-/keyboard-8.0.2.tgz",
- "integrity": "sha512-he6xKmTBp5AhVrWJeEi6RYkJ25FjLLdNruBU2wafpITk3Nb7UdzOj96x3K6etFuEj8/rtn9WXBTs1o2XA86A1A==",
+ "version": "8.0.3",
+ "resolved": "https://registry.npmjs.org/@capacitor/keyboard/-/keyboard-8.0.3.tgz",
+ "integrity": "sha512-27Bv5/2w1Ss2njguBgTS98O0Bb8DRJhAARyzXYib0JlT/n6BrJw/EZ0CokM4C8GFUjFDjJnEKF1Ie01buTMEXQ==",
"dev": true,
"license": "MIT",
"peerDependencies": {
diff --git a/core/package.json b/core/package.json
index 4321a90686f..fbdf88d9ecb 100644
--- a/core/package.json
+++ b/core/package.json
@@ -1,6 +1,6 @@
{
"name": "@ionic/core",
- "version": "8.8.4",
+ "version": "8.8.5",
"description": "Base components for Ionic",
"engines": {
"node": ">= 16"
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
+
+
+
+