Skip to content

fix(desktop): quiet chrome hover off teal fill and cream wash #1203

fix(desktop): quiet chrome hover off teal fill and cream wash

fix(desktop): quiet chrome hover off teal fill and cream wash #1203

Workflow file for this run

name: CI
# Triggers (see docs/ci.md):
# - pull_request into develop or main — this is where required checks run
# - push to main — post-promotion record; Release does not wait on this workflow
# Push to develop is omitted so a squash-merge does not pay the suite twice.
on:
push:
branches: [main]
pull_request:
branches: [main, develop]
concurrency:
group: ci-${{ github.ref }}
cancel-in-progress: true
env:
NODE_VERSION: '22'
jobs:
# Release back-merges exist for ancestry (docs/RELEASE.md), not product
# changes. Skip the heavy suite but keep required check names green:
# lint, test, typecheck, e2e, security. Trusted PRs only: github-actions[bot]
# into develop. Branch prefix matches automerge.yml; title covers
# github-actions sync-develop PRs. Do not trust the prefix alone.
ci-gate:
runs-on: ubuntu-latest
outputs:
skip_heavy: ${{ steps.detect.outputs.skip_heavy }}
steps:
- id: detect
env:
EVENT_NAME: ${{ github.event_name }}
HEAD_REF: ${{ github.event.pull_request.head.ref }}
BASE_REF: ${{ github.event.pull_request.base.ref }}
PR_TITLE: ${{ github.event.pull_request.title }}
PR_USER: ${{ github.event.pull_request.user.login }}
run: |
skip=false
if [ "$EVENT_NAME" = "pull_request" ] && \
[ "$PR_USER" = "github-actions[bot]" ] && \
[ "$BASE_REF" = "develop" ]; then
case "$HEAD_REF" in
chore/backmerge-*) skip=true ;;
esac
if [ "$PR_TITLE" = "chore(release): merge main into develop" ]; then
skip=true
fi
fi
echo "skip_heavy=${skip}" >> "$GITHUB_OUTPUT"
if [ "$skip" = "true" ]; then
echo "Release back-merge — heavy CI will report success without install/test/e2e."
else
echo "Full CI."
fi
# ── Shared install + cache ─────────────────────────
setup:
needs: ci-gate
if: needs.ci-gate.outputs.skip_heavy != 'true'
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v5
- name: Setup pnpm
uses: pnpm/action-setup@v5
- name: Setup Node.js
uses: actions/setup-node@v5
with:
node-version: ${{ env.NODE_VERSION }}
cache: 'pnpm'
- name: Force HTTPS for GitHub git dependencies
run: git config --global 'url.https://github.com/.insteadOf' 'git@github.com:'
# CI only runs lint/test/typecheck/build — none of those exercise
# better-sqlite3 at runtime. Skipping postinstall avoids apps/desktop's
# electron-builder install-app-deps step, which rebuilds better-sqlite3
# against the bundled Electron headers — that rebuild can break the
# whole setup job when better-sqlite3 lags an Electron major (see the
# v0.15.0 incident where Electron 42's V8 API broke better-sqlite3
# 12.10.0). Same pattern used in release.yml and deploy-api.yml.
- name: Install dependencies
run: pnpm install --frozen-lockfile --ignore-scripts
- name: Cache node_modules
uses: actions/cache/save@v5
with:
path: |
node_modules
apps/*/node_modules
packages/*/node_modules
key: modules-${{ runner.os }}-${{ hashFiles('pnpm-lock.yaml') }}
# ── Tier 1: Lint + Format ──────────────────────────
lint:
needs: [ci-gate, setup]
# always() is required so a skipped setup (back-merge) does not skip
# this job. needs.setup.result == 'success' still blocks a failed setup.
if: ${{ always() && !cancelled() && needs.ci-gate.result == 'success' && (needs.ci-gate.outputs.skip_heavy == 'true' || needs.setup.result == 'success') }}
runs-on: ubuntu-latest
steps:
- name: Skip heavy CI for release back-merge
if: needs.ci-gate.outputs.skip_heavy == 'true'
run: echo "Back-merge PR — required check lint reports success without the suite."
- uses: actions/checkout@v5
if: needs.ci-gate.outputs.skip_heavy != 'true'
- uses: pnpm/action-setup@v5
if: needs.ci-gate.outputs.skip_heavy != 'true'
- uses: actions/setup-node@v5
if: needs.ci-gate.outputs.skip_heavy != 'true'
with:
node-version: ${{ env.NODE_VERSION }}
- name: Restore node_modules
if: needs.ci-gate.outputs.skip_heavy != 'true'
uses: actions/cache/restore@v5
with:
path: |
node_modules
apps/*/node_modules
packages/*/node_modules
key: modules-${{ runner.os }}-${{ hashFiles('pnpm-lock.yaml') }}
- name: ESLint
if: needs.ci-gate.outputs.skip_heavy != 'true'
run: pnpm lint
- name: Prettier
if: needs.ci-gate.outputs.skip_heavy != 'true'
run: pnpm format:check
# PR title commitlint moved to .github/workflows/pr-title.yml so it
# exposes a stable, standalone status-check name for branch protection
# (squash-merge makes the PR title the release-trigger commit message).
# ── Tier 1: Tests + Coverage ───────────────────────
test:
needs: [ci-gate, setup]
if: ${{ always() && !cancelled() && needs.ci-gate.result == 'success' && (needs.ci-gate.outputs.skip_heavy == 'true' || needs.setup.result == 'success') }}
runs-on: ubuntu-latest
steps:
- name: Skip heavy CI for release back-merge
if: needs.ci-gate.outputs.skip_heavy == 'true'
run: echo "Back-merge PR — required check test reports success without the suite."
- uses: actions/checkout@v5
if: needs.ci-gate.outputs.skip_heavy != 'true'
- uses: pnpm/action-setup@v5
if: needs.ci-gate.outputs.skip_heavy != 'true'
- uses: actions/setup-node@v5
if: needs.ci-gate.outputs.skip_heavy != 'true'
with:
node-version: ${{ env.NODE_VERSION }}
- name: Restore node_modules
if: needs.ci-gate.outputs.skip_heavy != 'true'
uses: actions/cache/restore@v5
with:
path: |
node_modules
apps/*/node_modules
packages/*/node_modules
key: modules-${{ runner.os }}-${{ hashFiles('pnpm-lock.yaml') }}
- name: Run tests
if: needs.ci-gate.outputs.skip_heavy != 'true'
run: pnpm test
- name: Build packages
if: needs.ci-gate.outputs.skip_heavy != 'true'
run: pnpm build
- name: Upload coverage
if: ${{ always() && needs.ci-gate.outputs.skip_heavy != 'true' }}
uses: actions/upload-artifact@v5
with:
name: coverage-report
path: |
packages/*/coverage/
apps/*/coverage/
if-no-files-found: ignore
# ── Tier 1: Typecheck ──────────────────────────────
typecheck:
needs: [ci-gate, setup]
if: ${{ always() && !cancelled() && needs.ci-gate.result == 'success' && (needs.ci-gate.outputs.skip_heavy == 'true' || needs.setup.result == 'success') }}
runs-on: ubuntu-latest
steps:
- name: Skip heavy CI for release back-merge
if: needs.ci-gate.outputs.skip_heavy == 'true'
run: echo "Back-merge PR — required check typecheck reports success without the suite."
- uses: actions/checkout@v5
if: needs.ci-gate.outputs.skip_heavy != 'true'
- uses: pnpm/action-setup@v5
if: needs.ci-gate.outputs.skip_heavy != 'true'
- uses: actions/setup-node@v5
if: needs.ci-gate.outputs.skip_heavy != 'true'
with:
node-version: ${{ env.NODE_VERSION }}
- name: Restore node_modules
if: needs.ci-gate.outputs.skip_heavy != 'true'
uses: actions/cache/restore@v5
with:
path: |
node_modules
apps/*/node_modules
packages/*/node_modules
key: modules-${{ runner.os }}-${{ hashFiles('pnpm-lock.yaml') }}
- name: Build packages first
if: needs.ci-gate.outputs.skip_heavy != 'true'
run: pnpm build
- name: Typecheck all packages
if: needs.ci-gate.outputs.skip_heavy != 'true'
run: pnpm -r typecheck
- name: Typecheck desktop
if: needs.ci-gate.outputs.skip_heavy != 'true'
working-directory: apps/desktop
run: pnpm typecheck
# ── Tier 2: E2E (Playwright + Electron, xvfb on Linux) ─────────────────
# Must NOT restore the setup job's --ignore-scripts cache: that tree has
# no Electron binary and a Node-built better-sqlite3. A full install with
# scripts + the two explicit materialize steps is what makes electron.launch
# work. This job is required (no continue-on-error).
e2e:
needs: [ci-gate, setup]
if: ${{ always() && !cancelled() && needs.ci-gate.result == 'success' && (needs.ci-gate.outputs.skip_heavy == 'true' || needs.setup.result == 'success') }}
runs-on: ubuntu-latest
steps:
- name: Skip heavy CI for release back-merge
if: needs.ci-gate.outputs.skip_heavy == 'true'
run: echo "Back-merge PR — required check e2e reports success without Playwright."
- uses: actions/checkout@v5
if: needs.ci-gate.outputs.skip_heavy != 'true'
- uses: pnpm/action-setup@v5
if: needs.ci-gate.outputs.skip_heavy != 'true'
- uses: actions/setup-node@v5
if: needs.ci-gate.outputs.skip_heavy != 'true'
with:
node-version: ${{ env.NODE_VERSION }}
cache: 'pnpm'
# Same rewrite as the setup job. Dependabot-regenerated lockfiles
# resolve @electron/node-gyp as git@github.com: (SSH). Runners have
# no deploy key, so install dies in ~20s with "Permission denied
# (publickey)" — that is why Dependabot PRs fail e2e while develop
# (HTTPS tarball in the lockfile) stays green. See #544.
- name: Force HTTPS for GitHub git dependencies
if: needs.ci-gate.outputs.skip_heavy != 'true'
run: git config --global 'url.https://github.com/.insteadOf' 'git@github.com:'
- name: Install dependencies (with postinstall scripts)
if: needs.ci-gate.outputs.skip_heavy != 'true'
run: pnpm install --frozen-lockfile
- name: Materialize Electron binary
if: needs.ci-gate.outputs.skip_heavy != 'true'
working-directory: apps/desktop
run: node node_modules/electron/install.js
- name: Rebuild native modules for Electron
if: needs.ci-gate.outputs.skip_heavy != 'true'
run: pnpm --filter @dripnex/desktop exec electron-builder install-app-deps
- name: Install Playwright system deps
if: needs.ci-gate.outputs.skip_heavy != 'true'
working-directory: apps/desktop
run: npx playwright install-deps chromium
- name: Build desktop bundle
if: needs.ci-gate.outputs.skip_heavy != 'true'
run: pnpm --filter @dripnex/desktop build
- name: Run Playwright E2E (xvfb)
if: needs.ci-gate.outputs.skip_heavy != 'true'
working-directory: apps/desktop
run: xvfb-run --auto-servernum pnpm e2e
env:
CI: 'true'
- name: Upload Playwright report on failure
if: ${{ failure() && needs.ci-gate.outputs.skip_heavy != 'true' }}
uses: actions/upload-artifact@v5
with:
name: playwright-report
path: apps/desktop/playwright-report/
retention-days: 7
# ── Tier 3: Security audit ─────────────────────────
security:
needs: [ci-gate, setup]
if: ${{ always() && !cancelled() && needs.ci-gate.result == 'success' && (needs.ci-gate.outputs.skip_heavy == 'true' || needs.setup.result == 'success') }}
runs-on: ubuntu-latest
steps:
- name: Skip heavy CI for release back-merge
if: needs.ci-gate.outputs.skip_heavy == 'true'
run: echo "Back-merge PR — required check security reports success without the audit."
- uses: actions/checkout@v5
if: needs.ci-gate.outputs.skip_heavy != 'true'
- uses: pnpm/action-setup@v5
if: needs.ci-gate.outputs.skip_heavy != 'true'
- uses: actions/setup-node@v5
if: needs.ci-gate.outputs.skip_heavy != 'true'
with:
node-version: ${{ env.NODE_VERSION }}
- name: Restore node_modules
if: needs.ci-gate.outputs.skip_heavy != 'true'
uses: actions/cache/restore@v5
with:
path: |
node_modules
apps/*/node_modules
packages/*/node_modules
key: modules-${{ runner.os }}-${{ hashFiles('pnpm-lock.yaml') }}
- name: Audit dependencies
if: needs.ci-gate.outputs.skip_heavy != 'true'
run: pnpm audit --prod --audit-level=high || true
- name: Check licenses
if: needs.ci-gate.outputs.skip_heavy != 'true'
run: |
npx license-checker --production --failOn "GPL-3.0;AGPL-3.0;SSPL-1.0" --summary || true
# ── Tier 2: Bundle size tracking ───────────────────
bundle-size:
needs: [ci-gate, setup]
if: github.event_name == 'pull_request' && needs.ci-gate.outputs.skip_heavy != 'true'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- uses: pnpm/action-setup@v5
- uses: actions/setup-node@v5
with:
node-version: ${{ env.NODE_VERSION }}
- name: Restore node_modules
uses: actions/cache/restore@v5
with:
path: |
node_modules
apps/*/node_modules
packages/*/node_modules
key: modules-${{ runner.os }}-${{ hashFiles('pnpm-lock.yaml') }}
- name: Build all packages
run: pnpm build
- name: Report package sizes
run: |
echo "## 📦 Package Sizes" >> "$GITHUB_STEP_SUMMARY"
echo "" >> "$GITHUB_STEP_SUMMARY"
echo "| Package | Size |" >> "$GITHUB_STEP_SUMMARY"
echo "|---------|------|" >> "$GITHUB_STEP_SUMMARY"
for dir in packages/*/dist; do
if [ -d "$dir" ]; then
pkg=$(basename "$(dirname "$dir")")
size=$(du -sh "$dir" 2>/dev/null | cut -f1)
echo "| $pkg | $size |" >> "$GITHUB_STEP_SUMMARY"
fi
done
# ── Tier 2: PR labels ─────────────────────────────
label:
runs-on: ubuntu-latest
if: github.event_name == 'pull_request'
permissions:
contents: read
pull-requests: write
steps:
- uses: actions/labeler@v6
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}