fix(desktop): quiet chrome hover off teal fill and cream wash #1203
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: 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 }} |