feat(ai-elements): add Convex UI parts adapter #372
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Tier B regression (preview) | |
| # The single highest-leverage check in this repo. Runs the full | |
| # Tier B Playwright suite (tests/e2e/exact-kit-parity-prod.spec.ts) | |
| # against the Vercel PR preview URL BEFORE merge. | |
| # | |
| # Why this matters: A9 took 4 separate PRs to land on prod earlier | |
| # today (#166, #167, #168, #170-retrigger), because each fix was | |
| # only verified against the live prod URL post-merge. Each fix | |
| # unblocked the next assertion, which we only saw after deploy. | |
| # Running Tier B against the preview URL on every PR catches that | |
| # class of failures as a single CI check on the FIRST PR. | |
| # | |
| # Strategy: | |
| # 1. Wait for Vercel's PR preview deployment to be Ready (poll | |
| # Vercel's deployments API, filter by commit SHA). | |
| # 2. Extract the deployment URL. | |
| # 3. Run Playwright with BASE_URL=$PREVIEW_URL. | |
| # | |
| # Required secrets: | |
| # - VERCEL_TOKEN: a Vercel access token with Read access to the | |
| # team. Create at https://vercel.com/account/tokens. | |
| # - VERCEL_TEAM_ID, VERCEL_PROJECT_ID: already in the project's | |
| # .vercel/project.json — duplicated here as secrets for the | |
| # CI runner which doesn't have a linked Vercel CLI. | |
| on: | |
| pull_request: | |
| branches: | |
| - main | |
| types: [opened, synchronize, reopened, ready_for_review] | |
| merge_group: | |
| workflow_dispatch: | |
| inputs: | |
| pr_number: | |
| description: "PR number to inspect for changed files when running manually" | |
| required: false | |
| head_ref: | |
| description: "Head branch to poll in Vercel when running manually" | |
| required: false | |
| head_sha: | |
| description: "Head SHA to prefer when running manually" | |
| required: false | |
| preview_url: | |
| description: "Override preview URL (skip Vercel polling)" | |
| required: false | |
| permissions: | |
| contents: read | |
| pull-requests: write | |
| concurrency: | |
| group: tier-b-preview-${{ github.event.pull_request.number || github.ref }} | |
| cancel-in-progress: true | |
| jobs: | |
| wait-and-test: | |
| name: Tier B vs preview URL | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 25 | |
| steps: | |
| - uses: actions/checkout@v6 | |
| - uses: actions/setup-node@v6 | |
| with: | |
| package-manager-cache: false | |
| node-version-file: ".nvmrc" | |
| - name: Install dependencies | |
| run: npm install --no-audit --no-fund | |
| - name: Detect Vercel-relevant changes | |
| id: relevant | |
| run: | | |
| # Mirror scripts/vercel-ignore-build.sh: if the PR's diff doesn't | |
| # touch any path Vercel actually serves, no preview will ever exist. | |
| # Don't block such PRs (docs-only, .github/-only, etc.). | |
| BUILD_RELEVANT=(src convex packages apps public server shared api scripts/vercel-build.sh vercel.json package.json package-lock.json tsconfig.json tsconfig.node.json vite.config.ts vite.config.mjs index.html) | |
| if [ -n "${OVERRIDE_URL:-}" ]; then | |
| echo "has_relevant=true" >> "$GITHUB_OUTPUT" | |
| echo "Manual preview URL supplied; running Tier B against it." | |
| exit 0 | |
| fi | |
| if [ -z "${PR_NUMBER:-}" ]; then | |
| echo "has_relevant=true" >> "$GITHUB_OUTPUT" | |
| echo "No PR number available for changed-file detection; assuming relevant." | |
| exit 0 | |
| fi | |
| # Get changed files for this PR | |
| CHANGED=$(gh pr diff "$PR_NUMBER" --name-only 2>/dev/null || echo "") | |
| if [ -z "$CHANGED" ]; then | |
| # Fallback: if we can't get the diff, assume relevant | |
| echo "has_relevant=true" >> "$GITHUB_OUTPUT" | |
| echo "Could not determine changed paths — assuming relevant." | |
| exit 0 | |
| fi | |
| # Strip test-only / non-shipping files BEFORE relevance detection. Vitest | |
| # tests, __tests__/__mocks__ fixtures, and *.stories never reach the Vercel | |
| # bundle, so a PR that ONLY touches them cannot change the preview — and | |
| # must NOT burn the ~10-min preview-resolve poll. Without this, a | |
| # convex/__tests__/*.test.ts PR matches the broad `convex` path and waits | |
| # the full 10 min to skip-green (the "why is Tier B taking forever" case). | |
| SHIPPING=$(echo "$CHANGED" | grep -vE '(\.test\.[cm]?tsx?$|\.spec\.[cm]?tsx?$|(^|/)__tests__/|(^|/)__mocks__/|\.stories\.[cm]?tsx?$)' || true) | |
| if [ -z "$SHIPPING" ]; then | |
| echo "has_relevant=false" >> "$GITHUB_OUTPUT" | |
| echo "::notice::PR touches only test/non-shipping files — no preview can change. Tier B skipped (instant)." | |
| exit 0 | |
| fi | |
| for path in "${BUILD_RELEVANT[@]}"; do | |
| if echo "$SHIPPING" | grep -qE "(^|/)${path}(/|$)"; then | |
| echo "has_relevant=true" >> "$GITHUB_OUTPUT" | |
| echo "Detected change in build-relevant path: ${path}" | |
| exit 0 | |
| fi | |
| done | |
| echo "has_relevant=false" >> "$GITHUB_OUTPUT" | |
| echo "::notice::PR touches no build-relevant paths — Vercel will not build a preview, Tier B skipped." | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| PR_NUMBER: ${{ github.event.pull_request.number || github.event.inputs.pr_number }} | |
| OVERRIDE_URL: ${{ github.event.inputs.preview_url }} | |
| - name: Resolve Vercel preview URL | |
| id: preview | |
| if: steps.relevant.outputs.has_relevant == 'true' | |
| env: | |
| VERCEL_TOKEN: ${{ secrets.VERCEL_TOKEN }} | |
| VERCEL_TEAM_ID: ${{ secrets.VERCEL_TEAM_ID }} | |
| VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID }} | |
| # GITHUB_SHA and GITHUB_REF are reserved runner variables and cannot be | |
| # overridden reliably. Keep the Vercel lookup inputs under distinct names. | |
| VERCEL_HEAD_SHA: ${{ github.event.pull_request.head.sha || github.event.inputs.head_sha || github.sha }} | |
| VERCEL_HEAD_REF: ${{ github.event.pull_request.head.ref || github.event.inputs.head_ref || github.ref_name }} | |
| OVERRIDE_URL: ${{ github.event.inputs.preview_url }} | |
| run: | | |
| if [ -n "$OVERRIDE_URL" ]; then | |
| echo "Using override URL: $OVERRIDE_URL" | |
| echo "preview_status=ready" >> "$GITHUB_OUTPUT" | |
| echo "url=$OVERRIDE_URL" >> "$GITHUB_OUTPUT" | |
| exit 0 | |
| fi | |
| if [ -z "$VERCEL_TOKEN" ]; then | |
| echo "::error::VERCEL_TOKEN secret missing — Tier B preview workflow can't poll Vercel." | |
| exit 1 | |
| fi | |
| # Poll for up to ~10 min for a Vercel preview deployment matching this PR. | |
| # | |
| # The query strategy went through two iterations: | |
| # v1 (broken): filter by the runner's merge SHA. After | |
| # update-branch / merge-commit injection, the SHA Vercel records | |
| # can drift from the PR head SHA → false-fail timeouts. | |
| # v2 (this version): filter by meta-githubCommitRef using the explicit | |
| # PR head ref, then require the deployment's metadata to match the | |
| # explicit PR head SHA exactly. | |
| # | |
| # Only READY for the exact PR head SHA is acceptable here. The changed- | |
| # file step already handles proven non-shipping diffs, so CANCELED, | |
| # ERROR, API errors, and resolver timeouts fail closed. | |
| deadline=$(( $(date +%s) + 600 )) | |
| # URL-encode the branch ref. Branches with "/" in the name (refactor/X, | |
| # fix/Y, test/Z) require encoding — without it, Vercel's API treats | |
| # the query as an empty match, returns no deployments, and the loop | |
| # below burns the full 10-min deadline before failing. With encoding, | |
| # the API returns the actual deployment within seconds. | |
| # (jq is preinstalled on ubuntu-latest GitHub runners.) | |
| encoded_ref=$(printf '%s' "$VERCEL_HEAD_REF" | jq -sRr @uri) | |
| while [ "$(date +%s)" -lt "$deadline" ]; do | |
| # Query by branch name (stable) AND limit=20 to catch the latest few | |
| # deployments on this branch even if Vercel created multiple builds. | |
| payload=$(curl -fsS -H "Authorization: Bearer $VERCEL_TOKEN" \ | |
| "https://api.vercel.com/v6/deployments?teamId=$VERCEL_TEAM_ID&projectId=$VERCEL_PROJECT_ID&meta-githubCommitRef=$encoded_ref&limit=20&target=preview") | |
| result=$(VERCEL_HEAD_SHA="$VERCEL_HEAD_SHA" node -e " | |
| let s=''; process.stdin.on('data',c=>s+=c).on('end',()=>{ | |
| try { | |
| const j = JSON.parse(s); | |
| const sha = process.env.VERCEL_HEAD_SHA || ''; | |
| if (!Array.isArray(j.deployments)) { | |
| throw new Error(j.error?.message || 'deployments array missing'); | |
| } | |
| const deployments = j.deployments; | |
| const state = (d) => d.readyState || d.state; | |
| const exactReady = deployments.find(d => { | |
| const m = d.meta || {}; | |
| return (m.githubCommitSha === sha || m.commitSha === sha) && state(d) === 'READY'; | |
| }); | |
| const exactError = deployments.find(d => { | |
| const m = d.meta || {}; | |
| return (m.githubCommitSha === sha || m.commitSha === sha) && state(d) === 'ERROR'; | |
| }); | |
| const exactCanceled = deployments.find(d => { | |
| const m = d.meta || {}; | |
| return (m.githubCommitSha === sha || m.commitSha === sha) && state(d) === 'CANCELED'; | |
| }); | |
| const dep = exactReady || exactError || exactCanceled; | |
| if (dep) { | |
| const st = state(dep); | |
| if (st === 'READY') console.log('READY:https://' + dep.url); | |
| else if (st === 'CANCELED') console.log('CANCELED:vercel-marked-ignored'); | |
| else if (st === 'ERROR') console.log('ERROR:vercel-build-failed'); | |
| } | |
| } catch(e) { | |
| console.error('Invalid Vercel deployments response:', e instanceof Error ? e.message : e); | |
| process.exitCode = 2; | |
| } | |
| }); | |
| " <<< "$payload") | |
| if [[ "$result" == READY:* ]]; then | |
| url="${result#READY:}" | |
| echo "Preview ready: $url" | |
| echo "preview_status=ready" >> "$GITHUB_OUTPUT" | |
| echo "url=$url" >> "$GITHUB_OUTPUT" | |
| exit 0 | |
| fi | |
| if [[ "$result" == CANCELED:* ]]; then | |
| echo "::error::Vercel canceled the exact build-relevant head SHA. Tier B cannot verify this shipping change." | |
| exit 1 | |
| fi | |
| if [[ "$result" == ERROR:* ]]; then | |
| echo "::error::Vercel preview build failed for this branch. Tier B can't run against a failed deploy. Investigate the Vercel build log." | |
| exit 1 | |
| fi | |
| sleep 15 | |
| done | |
| # A build-relevant PR without an exact-head deployment is unverifiable. | |
| echo "::error::No exact-head preview deployment found for branch '$VERCEL_HEAD_REF' at SHA '$VERCEL_HEAD_SHA' after 10min." | |
| exit 1 | |
| - name: Install Playwright browsers | |
| if: steps.preview.outputs.preview_status == 'ready' | |
| run: npx playwright install --with-deps chromium | |
| - name: Tier B summary | |
| if: always() | |
| run: | | |
| echo "preview_status=${{ steps.preview.outputs.preview_status || 'not_resolved' }}" >> "$GITHUB_STEP_SUMMARY" | |
| echo "preview_url=${{ steps.preview.outputs.url || 'none' }}" >> "$GITHUB_STEP_SUMMARY" | |
| - name: Require resolved preview for shipping changes | |
| if: always() && steps.relevant.outputs.has_relevant == 'true' && steps.preview.outputs.preview_status != 'ready' | |
| run: | | |
| echo "::error::Build-relevant change did not produce a real READY preview; Tier B fails closed." | |
| exit 1 | |
| - name: Run Tier B regression + one-flow regression against preview | |
| if: steps.preview.outputs.preview_status == 'ready' | |
| env: | |
| BASE_URL: ${{ steps.preview.outputs.url }} | |
| VERCEL_AUTOMATION_BYPASS_SECRET: ${{ secrets.VERCEL_AUTOMATION_BYPASS_SECRET }} | |
| run: | | |
| echo "BASE_URL=$BASE_URL" | |
| if [ -z "$VERCEL_AUTOMATION_BYPASS_SECRET" ]; then | |
| echo "::error::VERCEL_AUTOMATION_BYPASS_SECRET is required to test the protected READY preview." | |
| exit 1 | |
| fi | |
| # exact-kit-parity-prod: visual selector parity (per-PR canonical-component check) | |
| # one-flow-regression: full surface tour + A9 fallback + interactive chip behavior | |
| npx playwright test tests/e2e/vercel-preview-security.spec.ts tests/e2e/exact-kit-parity-prod.spec.ts tests/e2e/one-flow-regression.spec.ts --project=chromium --reporter=line | |
| - name: Upload Playwright artifacts on failure | |
| if: failure() | |
| uses: actions/upload-artifact@v7 | |
| with: | |
| name: tier-b-preview-${{ github.run_id }} | |
| path: | | |
| playwright-report/ | |
| test-results/ | |
| retention-days: 14 | |
| - name: Comment failure on PR | |
| if: failure() && github.event_name == 'pull_request' | |
| uses: actions/github-script@v9 | |
| with: | |
| script: | | |
| const url = "${{ steps.preview.outputs.url }}" || "(preview URL unresolved)"; | |
| await github.rest.issues.createComment({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| issue_number: context.issue.number, | |
| body: [ | |
| `## Tier B regression failed against preview`, | |
| ``, | |
| `**Preview URL:** ${url}`, | |
| `**Run:** [${context.runId}](${context.serverUrl}/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId})`, | |
| ``, | |
| `Likely a kit-parity DOM regression. Check the Playwright report artifact attached to the run for the failing selector + screenshot.`, | |
| ``, | |
| `*This check would have caught all 4 A9 sessionRows iterations as a single PR — that's why it exists.*`, | |
| ].join('\n'), | |
| }); |