Skip to content

fix(gmail-browser): search result integrity — settled-view primitive … #56

fix(gmail-browser): search result integrity — settled-view primitive …

fix(gmail-browser): search result integrity — settled-view primitive … #56

Workflow file for this run

name: CI
on:
# Gate PRs, and the mainline itself after merge. Feature-branch pushes are
# covered by their PR run, so this avoids the push+pull_request double-run.
push:
branches: [master]
pull_request:
workflow_dispatch:
# Least privilege: CI only needs to read the checkout. Overrides the repo
# default token scope so a compromised action can't write to the repo.
permissions:
contents: read
concurrency:
group: ci-${{ github.ref }}
# Cancel superseded PR runs, but let every master run finish so the
# mainline history has no coverage gaps between rapid merges.
cancel-in-progress: ${{ github.ref != 'refs/heads/master' }}
env:
CARGO_TERM_COLOR: always
# The `--tests` gate links 100+ integration-test binaries; with debug
# symbols (the bulk of a debug binary) that `target/` exhausts the runner's
# disk ("No space left on device"). Debug symbols are useless in CI, so drop
# them — much smaller target/, faster linking, identical test behavior.
CARGO_PROFILE_DEV_DEBUG: "0"
CARGO_PROFILE_TEST_DEBUG: "0"
# Runners ship tmux, so `require_tmux_or_skip` would RUN the TUI snapshot
# tests — which are terminal-/timing-fragile in CI and fail deterministically.
# Force them to skip; they still run locally for anyone who wants them.
PUFFER_SKIP_TMUX_TESTS: "1"
jobs:
rust:
name: Rust (build + test)
runs-on: ubuntu-latest
# Safety net: the workspace suite runs in ~3 min; a stuck test (e.g. a
# blocked global-singleton channel) must not burn the 6h GitHub default.
timeout-minutes: 25
steps:
- uses: actions/checkout@v7
- name: Install system dependencies
run: sudo apt-get update && sudo apt-get install -y pkg-config libssl-dev
- uses: dtolnay/rust-toolchain@stable
with:
components: rustfmt, clippy
- uses: Swatinem/rust-cache@v2
with:
# Only master writes to the 10GB Actions cache quota; PR branches
# read it but don't evict the mainline baseline with per-PR entries.
save-if: ${{ github.ref == 'refs/heads/master' }}
- uses: taiki-e/install-action@nextest
# --- Hard gate: compile the whole workspace including test, integration,
# example, and bench targets (`--all-targets`), so a compile break outside
# production code (e.g. in a `crates/*/tests/` file) fails CI. This is
# also the only step that truly LINKS every target; nextest and clippy
# below share its artifacts, so it costs little beyond that link. ---
- name: Build
run: cargo build --workspace --all-targets --locked
# --- Hard gate: the whole-workspace test suite via nextest (process-
# per-test isolation, ~40% faster in CI than cargo test, and the `ci`
# profile in .config/nextest.toml kills any single wedged test instead
# of letting it stall the job). Runs lib + bin unit tests (incl. the
# bin-only `puffer-cli`) AND the integration test targets under
# `crates/*/tests/`. Runs in parallel — `ConfigPaths::discover` isolates
# `user_config_dir` under the OS temp dir, so no race on the real
# ~/.puffer.
#
# Integration tests that need infra CI does not provide self-exclude:
# tmux TUI tests are skipped via PUFFER_SKIP_TMUX_TESTS, and the few
# needing the workflow-runtime Docker image / a real browser are
# `#[ignore]`d. Run the full set locally with tmux installed. ---
- name: Test (nextest)
run: cargo nextest run --workspace --locked --profile ci
# nextest cannot run doctests; without this step any future doctest
# would silently never execute in CI. Currently 0 doctests, ~30s warm.
- name: Doc tests
run: cargo test --workspace --doc --locked
# --- Hard gate: formatting drift was settled in one workspace-wide
# rustfmt commit; from here on any drift fails fast. ---
- name: Rustfmt
run: cargo fmt --all --check
# --- Hard gate: `correctness` flags real defects (bad comparisons,
# ignored #[must_use], always-true/false conditions) and `suspicious`
# catches bug-adjacent patterns like MutexGuard held across await.
# Style/pedantic warnings stay informational and never block. ---
- name: Clippy (correctness + suspicious gate)
run: cargo clippy --workspace --all-targets --locked -- -D clippy::correctness -D clippy::suspicious
desktop:
name: Desktop (build + check)
runs-on: ubuntu-latest
timeout-minutes: 15
defaults:
run:
working-directory: apps/puffer-desktop
steps:
- uses: actions/checkout@v7
- uses: actions/setup-node@v6
with:
# Single source of truth for the Node version (24 = Active LTS
# until 2028-04; Node 20 went EOL 2026-04-30).
node-version-file: apps/puffer-desktop/.nvmrc
cache: npm
cache-dependency-path: apps/puffer-desktop/package-lock.json
- name: Install deps
run: npm ci
# --- Hard gates: all currently green, so the desktop job is now a real
# gate (previously every step was informational and could never fail). ---
- name: Build (vite)
run: npm run build
- name: svelte-check
run: npm run check
# Pure-node config/asset assertions (no browser). The Playwright UI
# suites run in the separate desktop-ui job.
- name: Node tests
run: |
npm run test:tauri-config
npm run test:sidebar-css
npm run test:provider-visuals
# Detects whether a PR touches the desktop app so desktop-ui can skip on
# Rust-only PRs. Master pushes always run the suite (no mainline gaps).
# Plain `gh api` instead of a third-party paths-filter action: one fewer
# supply-chain dependency and no Node-runtime deprecation churn.
desktop-changes:
name: Desktop change filter
runs-on: ubuntu-latest
timeout-minutes: 5
permissions:
contents: read
pull-requests: read
outputs:
desktop: ${{ steps.filter.outputs.desktop }}
steps:
- name: List PR files and match desktop paths
id: filter
env:
GH_TOKEN: ${{ github.token }}
run: |
set -euo pipefail
if [ "${{ github.event_name }}" != "pull_request" ]; then
echo "desktop=true" >> "$GITHUB_OUTPUT"
exit 0
fi
files=$(gh api "repos/${{ github.repository }}/pulls/${{ github.event.pull_request.number }}/files" \
--paginate --jq '.[].filename')
if echo "$files" | grep -qE '^(apps/puffer-desktop/|\.github/workflows/ci\.yml$)'; then
echo "desktop=true" >> "$GITHUB_OUTPUT"
else
echo "desktop=false" >> "$GITHUB_OUTPUT"
fi
desktop-ui:
name: Desktop UI (playwright ${{ matrix.shard }}/2)
runs-on: ubuntu-latest
timeout-minutes: 20
needs: desktop-changes
if: github.event_name != 'pull_request' || needs.desktop-changes.outputs.desktop == 'true'
strategy:
# One shard's failure should not cancel the other: both halves' results
# tell you whether a red is isolated or systemic.
fail-fast: false
matrix:
shard: [1, 2]
defaults:
run:
working-directory: apps/puffer-desktop
steps:
- uses: actions/checkout@v7
- uses: actions/setup-node@v6
with:
node-version-file: apps/puffer-desktop/.nvmrc
cache: npm
cache-dependency-path: apps/puffer-desktop/package-lock.json
- name: Install deps
run: npm ci
- name: Install Playwright Chromium
run: npx playwright install --with-deps chromium
# Full mocked-daemon UI suite, split file-wise into two parallel shards
# (~4-5 min each vs ~9 min unsharded on the 2-core runners). Playwright
# starts its own vite webServer; specs run against FakeDaemon.
# NOTE: if tests/visual/ gains macOS-only screenshot baselines, exclude
# it here (see AGENTS.md — visual baselines diverge on ubuntu runners).
- name: Playwright UI suite (shard ${{ matrix.shard }}/2)
run: npx playwright test --shard=${{ matrix.shard }}/2
- name: Upload failure artifacts
if: failure()
uses: actions/upload-artifact@v4
with:
name: playwright-test-results-shard-${{ matrix.shard }}
path: apps/puffer-desktop/test-results/
retention-days: 7
desktop-backend:
name: Desktop backend (test + clippy)
runs-on: ubuntu-latest
# `apps/puffer-desktop/src-tauri` is a separate Cargo workspace that the
# root `cargo build --workspace` skips. Its 100+ unit tests were never
# run in CI (a plain `cargo check` doesn't even compile #[cfg(test)]
# code) — one of them rotted unnoticed. Runs in parallel with the other
# jobs, so this full gate has no critical-path cost.
timeout-minutes: 20
steps:
- uses: actions/checkout@v7
- name: Install system dependencies
run: |
sudo apt-get update
sudo apt-get install -y pkg-config libssl-dev libwebkit2gtk-4.1-dev \
libgtk-3-dev libayatana-appindicator3-dev librsvg2-dev
- uses: dtolnay/rust-toolchain@stable
with:
components: rustfmt, clippy
- uses: Swatinem/rust-cache@v2
with:
workspaces: apps/puffer-desktop/src-tauri
save-if: ${{ github.ref == 'refs/heads/master' }}
- uses: taiki-e/install-action@nextest
- name: Test (nextest, src-tauri)
run: cargo nextest run --manifest-path apps/puffer-desktop/src-tauri/Cargo.toml --locked --profile ci
- name: Rustfmt (src-tauri)
run: cargo fmt --all --check --manifest-path apps/puffer-desktop/src-tauri/Cargo.toml
# Same deny groups as the root workspace so both codebases meet one bar.
- name: Clippy (correctness + suspicious gate)
run: cargo clippy --manifest-path apps/puffer-desktop/src-tauri/Cargo.toml --all-targets --locked -- -D clippy::correctness -D clippy::suspicious