Skip to content

fix(ui): add a content-shaped loading skeleton to the analytics dashboard #2955

fix(ui): add a content-shaped loading skeleton to the analytics dashboard

fix(ui): add a content-shaped loading skeleton to the analytics dashboard #2955

Workflow file for this run

name: UI Preview Build
# Build half of the fork-safe per-PR preview pipeline. Runs for EVERY PR — including forks — and
# deliberately WITHOUT secrets (fork PRs get a read-only token): it only produces the built `dist`
# artifact. The trusted `ui-preview-deploy.yml` (triggered on workflow_run) then deploys that artifact
# WITH secrets and records the GitHub Deployment that Reviewbot reads for the "after" screenshot.
#
# Why split: a preview requires building the PR's UI, and building runs the PR's (possibly fork-authored)
# code. Doing that here with no secret access — and deploying the resulting bundle in a separate trusted
# step that never executes fork code — is the standard way to give fork PRs previews without exposing
# Cloudflare credentials to untrusted code.
on:
pull_request:
# Explicit list because the default (opened/synchronize/reopened) omits ready_for_review -- once the
# build job below skips draft PRs, marking a PR ready must itself trigger a real preview build (#6670),
# not wait for the next push. Mirrors ci.yml's pull_request.types comment/list exactly.
types: [opened, synchronize, reopened, ready_for_review]
paths:
- "apps/loopover-ui/**"
- "packages/**"
permissions:
contents: read
concurrency:
group: ui-preview-build-${{ github.event.pull_request.number }}
cancel-in-progress: true
jobs:
build:
name: Build UI preview artifact
# Skip draft PRs (#6670, anti-abuse): a full npm ci + UI build on every push, including a force-push, to
# a PR nobody has marked ready for review yet. Mirrors ci.yml's validate-code guard; draft != true also
# gates push runs correctly (github.event.pull_request is unset there, so the property access evaluates
# to null, and null != true is true) -- kept even though this workflow has no push trigger today, so the
# condition stays correct if one is ever added.
if: ${{ github.event_name == 'push' || github.event.pull_request.draft != true }}
runs-on: ubuntu-latest
timeout-minutes: 20
steps:
- name: Checkout PR head
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
with:
ref: ${{ github.event.pull_request.head.sha }}
persist-credentials: false
- name: Setup Node
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6
with:
node-version-file: .nvmrc
cache: npm
- name: Install dependencies
run: npm ci
- name: Build UI
env:
VITE_LOOPOVER_API_ORIGIN: https://api.loopover.ai
# Preview-only: enables the synthetic demo session (useSession().signInPreview) so reviewbot can
# screenshot the authenticated /app/* dashboard via ?preview=1 instead of the sign-in wall. The
# production build (ui-deploy.yml) does NOT set this, so the escape hatch is dead-code-eliminated
# from prod. (#authed-route-preview)
VITE_PREVIEW: "1"
run: npm run ui:build
# The trusted deploy workflow downloads this by name + run-id. It contains only the built bundle
# (server/ + client/) — no secrets, no source needed downstream.
- name: Upload built UI artifact
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: ui-preview-dist
path: apps/loopover-ui/dist
if-no-files-found: error
retention-days: 1