fix(web): full ARIA-modal isolation for the mobile detail pane (TASK-2131) #2934
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: CI | |
| on: | |
| push: | |
| branches: [main] | |
| pull_request: | |
| branches: [main] | |
| concurrency: | |
| group: ci-${{ github.ref }} | |
| cancel-in-progress: true | |
| permissions: | |
| contents: read | |
| # All third-party Actions are pinned to a 40-char commit SHA with a trailing | |
| # '# vX.Y.Z' comment so a compromised maintainer or moved tag cannot silently | |
| # execute attacker code in CI. Bump the SHA + comment together when updating. | |
| jobs: | |
| go: | |
| name: Go | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | |
| - uses: actions/setup-go@924ae3a1cded613372ab5595356fb5720e22ba16 # v6.5.0 | |
| with: | |
| go-version: "1.26" | |
| - name: Allow Go to fetch the toolchain go.mod pins | |
| # actions/setup-go unconditionally exports GOTOOLCHAIN=local, which | |
| # forbids Go from fetching the toolchain go.mod requires. go.mod pins | |
| # `go 1.26.5`, but setup-go's "1.26" resolves to the newest patch in | |
| # its manifest (1.26.4 here), so every `go` command fails with | |
| # "go.mod requires go >= 1.26.5 (running go 1.26.4)". Written AFTER | |
| # setup-go so it wins the $GITHUB_ENV last-write; `auto` lets Go pull | |
| # 1.26.5 on demand. Added after #896 raised the go.mod floor. | |
| run: echo "GOTOOLCHAIN=auto" >> "$GITHUB_ENV" | |
| - name: Create web build placeholder for embed | |
| run: mkdir -p web/build && echo "placeholder" > web/build/.gitkeep | |
| - name: Run go vet | |
| run: go vet ./... | |
| - name: Run golangci-lint | |
| # only-new-issues: false means CI fails on ANY linter finding, | |
| # not just findings on PR-changed lines. The IDEA-732 cleanup | |
| # (PRs #247/#249/#251/#252) cleared the existing findings under | |
| # the configured linter set in .golangci.yml — staticcheck SA*, | |
| # govet, ineffassign, gofmt, and the standalone `unused` linter | |
| # (which reports U1000). Flipping the gate now prevents | |
| # regression drift going forward. | |
| # v2 of golangci-lint is required because v1 is capped at older | |
| # Go releases that we no longer support. | |
| uses: golangci/golangci-lint-action@ba0d7d2ec06a0ea1cb5fa41b2e4a3ab91d21278a # v9.3.0 | |
| with: | |
| version: v2.11.4 | |
| args: --timeout=5m | |
| only-new-issues: false | |
| # Cap the blast radius of a stale-cache event to ~24h. The action's | |
| # cache stores the resolved issue list from a prior pass; if any | |
| # one pass writes degenerate results (analyzer upgrade, plugin | |
| # reset, sub-package version drift), every downstream restore | |
| # replays that list verbatim until the cache key rotates. PR #635 | |
| # hit exactly this — 30+ SA5011/SA4023 false positives against | |
| # unchanged code while local (cold-cache) runs reported 0 issues. | |
| # Default is 7 days; 1 day still keeps most runs cache-hot while | |
| # guaranteeing daily refresh. See BUG-1624. | |
| cache-invalidation-interval: "1" | |
| - name: Run govulncheck | |
| # Fails the build on any known vulnerability in a package we | |
| # actually reach via the call graph. Net-positive: catches CVEs | |
| # in indirect deps early, without the noise of hitting every | |
| # stale entry in our dependency tree. | |
| # | |
| # Runs in BINARY mode against a freshly-built pad binary rather than | |
| # source mode (`govulncheck ./...`). Source mode builds an SSA call- | |
| # graph over the whole dependency tree and can balloon to multiple GB | |
| # of RAM (BUG-2084); binary mode reads the binary's symbol table — a | |
| # fraction of the memory, still call-graph-precise, and detects stdlib | |
| # vulns from the Go version stamped in the binary. The "Create web | |
| # build placeholder for embed" step above satisfies the //go:embed so | |
| # the scan build succeeds. Mirrors the Makefile `vuln` target — keep | |
| # the two in sync. | |
| # | |
| # Pinned to a specific govulncheck release. Track upstream in | |
| # Pad's workspace; bump intentionally so an upstream behavior | |
| # change can't break unrelated PRs. Update via: | |
| # go install golang.org/x/vuln/cmd/govulncheck@<new-tag> | |
| run: | | |
| go install golang.org/x/vuln/cmd/[email protected] | |
| go build -o pad-vulnscan ./cmd/pad | |
| "$(go env GOPATH)/bin/govulncheck" -mode binary pad-vulnscan | |
| - name: Run tests | |
| run: go test ./... | |
| - name: Run tests with race detector | |
| # Runs on both push-to-main AND pull_request. Previously gated to | |
| # main only because GitHub Actions minutes were billed on private | |
| # repos; the repo is public now, so PR minutes are free and we'd | |
| # rather catch race regressions on the contributing branch than | |
| # after merge. See BUG-1371 (also dropped test-only bcrypt cost | |
| # via TestMain so this step stays well under the 30m budget). | |
| # | |
| # Default 10m is tight: the full server-package suite under -race | |
| # measures ~13m locally on a developer laptop after BUG-851 (the | |
| # ipRateLimiter goroutine drain). The PLAN-866 attachment work | |
| # (image decode/encode/resize across thumbnail + transform tests) | |
| # pushes total race-step runtime past 20m on the GitHub-hosted | |
| # runner. BUG-1913: the suite organically grew past the old 30m | |
| # budget (734 server-package tests, ~30.3m under -race even on a | |
| # fast local machine; no single test exceeds 14s — aggregate | |
| # weight, not a hang), turning most main runs red. 45m restores | |
| # headroom; genuine deadlocks still hit this and produce the | |
| # goroutine-dump panic, just up to 15m later. The real fix | |
| # (cheaper suite: shared fixtures / sharding) is tracked on | |
| # BUG-1913. | |
| run: go test -race -timeout=45m ./... | |
| - name: Build binary | |
| run: go build -o pad ./cmd/pad | |
| - name: Verify binary runs | |
| run: ./pad --help | |
| native-smoke: | |
| name: Smoke (${{ matrix.os }}) | |
| runs-on: ${{ matrix.os }} | |
| timeout-minutes: 10 | |
| defaults: | |
| run: | |
| shell: pwsh | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: | |
| - os: macos-latest | |
| binary: pad | |
| path: ./pad | |
| - os: windows-latest | |
| binary: pad.exe | |
| path: .\pad.exe | |
| steps: | |
| - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | |
| - uses: actions/setup-go@924ae3a1cded613372ab5595356fb5720e22ba16 # v6.5.0 | |
| with: | |
| go-version-file: go.mod | |
| - name: Create web build placeholder | |
| run: | | |
| New-Item -ItemType Directory -Force web/build | Out-Null | |
| Set-Content web/build/index.html '<!doctype html>' | |
| - name: Build binary | |
| run: go build -o "${{ matrix.binary }}" ./cmd/pad | |
| - name: Verify help output | |
| run: '& "${{ matrix.path }}" --help' | |
| - name: Run CLI smoke test | |
| run: | | |
| $ErrorActionPreference = 'Stop' | |
| $PSNativeCommandUseErrorActionPreference = $true | |
| $binary = '${{ matrix.path }}' | |
| $env:PAD_DATA_DIR = Join-Path $env:RUNNER_TEMP 'pad-smoke' | |
| $env:PAD_BYPASS_SETUP_TOKEN = 'true' | |
| $stdout = Join-Path $env:RUNNER_TEMP 'pad-server.out.log' | |
| $stderr = Join-Path $env:RUNNER_TEMP 'pad-server.err.log' | |
| $server = Start-Process -FilePath $binary -ArgumentList 'server','start','--port','17777' -PassThru -RedirectStandardOutput $stdout -RedirectStandardError $stderr | |
| try { | |
| $ready = $false | |
| for ($attempt = 0; $attempt -lt 60; $attempt++) { | |
| try { | |
| Invoke-WebRequest -UseBasicParsing http://127.0.0.1:17777/api/v1/health | Out-Null | |
| $ready = $true | |
| break | |
| } catch { | |
| Start-Sleep -Seconds 1 | |
| } | |
| } | |
| if (-not $ready) { | |
| Get-Content $stdout, $stderr -ErrorAction SilentlyContinue | |
| throw 'Pad server did not become ready' | |
| } | |
| & $binary auth configure --mode local --port 17777 | |
| & $binary auth setup --email [email protected] --name Smoke --password 'SmokePass123!' | |
| & $binary workspace create Smoke --slug smoke --template startup | |
| $item = (& $binary --workspace smoke --format json item create task 'Native smoke item') | ConvertFrom-Json | |
| $shown = (& $binary --workspace smoke --format json item show $item.ref) | ConvertFrom-Json | |
| if ($shown.title -ne 'Native smoke item') { | |
| throw "Unexpected item title: $($shown.title)" | |
| } | |
| } finally { | |
| Stop-Process -Id $server.Id -ErrorAction SilentlyContinue | |
| } | |
| go-postgres: | |
| name: Go (PostgreSQL) | |
| runs-on: ubuntu-latest | |
| services: | |
| postgres: | |
| image: postgres:17-alpine | |
| env: | |
| POSTGRES_USER: pad | |
| POSTGRES_PASSWORD: pad | |
| POSTGRES_DB: pad | |
| ports: | |
| - 5432:5432 | |
| options: >- | |
| --health-cmd "pg_isready -U pad" | |
| --health-interval 5s | |
| --health-timeout 3s | |
| --health-retries 10 | |
| steps: | |
| - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | |
| - uses: actions/setup-go@924ae3a1cded613372ab5595356fb5720e22ba16 # v6.5.0 | |
| with: | |
| go-version: "1.26" | |
| - name: Allow Go to fetch the toolchain go.mod pins | |
| # See the identical step in the `go` job — setup-go pins | |
| # GOTOOLCHAIN=local, blocking the 1.26.5 fetch go.mod requires. | |
| run: echo "GOTOOLCHAIN=auto" >> "$GITHUB_ENV" | |
| - name: Create web build placeholder for embed | |
| run: mkdir -p web/build && echo "placeholder" > web/build/.gitkeep | |
| - name: Run tests against PostgreSQL | |
| env: | |
| PAD_TEST_POSTGRES_URL: "postgres://pad:pad@localhost:5432/pad?sslmode=disable" | |
| run: go test ./... -count=1 | |
| - name: Run tests with race detector against PostgreSQL | |
| env: | |
| PAD_TEST_POSTGRES_URL: "postgres://pad:pad@localhost:5432/pad?sslmode=disable" | |
| # Runs on both push-to-main AND pull_request — see SQLite race-step | |
| # comment for the public-repo / BUG-1371 reasoning. | |
| # | |
| # Headroom over the default 10m. PostgreSQL adds latency on | |
| # every CREATE/DROP, and the PLAN-866 attachment work pushed the | |
| # cumulative wall over 20m. The bootstrap-user bcrypt cost that | |
| # blew past 30m on main (BUG-1371) is now handled by TestMain | |
| # dropping the cost to bcrypt.MinCost for test binaries. | |
| # BUG-1913: raised 30m → 45m alongside the SQLite step — the | |
| # suite's aggregate runtime crossed the old budget (this job | |
| # variant failed main at f235a04 with the same timeout panic). | |
| run: go test -race -timeout=45m ./... -count=1 | |
| web: | |
| name: Web | |
| runs-on: ubuntu-latest | |
| defaults: | |
| run: | |
| working-directory: web | |
| steps: | |
| - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | |
| - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 | |
| with: | |
| node-version: "24" | |
| cache: "npm" | |
| cache-dependency-path: web/package-lock.json | |
| - name: Install dependencies | |
| run: npm ci | |
| - name: Check coordinated Tiptap pins | |
| # The Y.Doc/ProseMirror schema is shared across @tiptap/core, | |
| # @tiptap/extension-collaboration, @tiptap/y-tiptap and @tiptap/pm. | |
| # A stray `npm update` that slides one of them can silently change | |
| # the persisted Y.Doc shape, producing divergent collab ops the | |
| # relay can't reconcile (see CLAUDE.md "Tiptap multi-package | |
| # coordinated bumps" and BUG-2009). This guard fails if any of them | |
| # loses its exact pin in package.json or resolves to more than one | |
| # version in the lockfile. | |
| run: npm run check:tiptap-pins | |
| - name: Audit npm dependencies (production, high+) | |
| # Fail the build on any HIGH or CRITICAL advisory in production deps. | |
| # Dev-only advisories are treated as informational — they don't ship | |
| # and fixing them can require waiting on upstream maintainers. | |
| run: npm audit --audit-level=high --omit=dev | |
| - name: Build | |
| run: npm run build | |
| - name: Type check (svelte-check) | |
| run: npm run check | |
| - name: Run web unit tests (vitest) | |
| run: npm run test | |
| e2e: | |
| name: E2E (Playwright) | |
| runs-on: ubuntu-latest | |
| # Build the binary + UI once and reuse across Playwright projects. | |
| # The suite is small (<10s at the time of writing — see TASK-733 for | |
| # follow-up coverage); the `timeout-minutes` cap is a sanity check. | |
| timeout-minutes: 10 | |
| steps: | |
| - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | |
| - uses: actions/setup-go@924ae3a1cded613372ab5595356fb5720e22ba16 # v6.5.0 | |
| with: | |
| go-version: "1.26" | |
| - name: Allow Go to fetch the toolchain go.mod pins | |
| # See the identical step in the `go` job — setup-go pins | |
| # GOTOOLCHAIN=local, blocking the 1.26.5 fetch go.mod requires | |
| # (the "Build pad binary" step below fails without this). | |
| run: echo "GOTOOLCHAIN=auto" >> "$GITHUB_ENV" | |
| - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 | |
| with: | |
| node-version: "24" | |
| cache: "npm" | |
| cache-dependency-path: web/package-lock.json | |
| - name: Install web dependencies | |
| working-directory: web | |
| run: npm ci | |
| - name: Build web UI | |
| working-directory: web | |
| run: npm run build | |
| - name: Build pad binary | |
| # Web build output is embedded via //go:embed; it must exist before | |
| # the Go build. The CI `go` job above builds against a placeholder, | |
| # which is fine for tests — for e2e we need the real embedded UI. | |
| run: go build -o pad ./cmd/pad | |
| # Playwright browser binaries are downloaded from cdn.playwright.dev, | |
| # which occasionally hangs (PR #635 hit two consecutive 10-minute | |
| # timeouts during a CDN slow patch — see BUG-1625). Cache them per | |
| # @playwright/test version so warm-cache runs skip the ~150 MB | |
| # Chromium download entirely; only the apt system libraries | |
| # (libatk, libnss, libcups, …) need a fresh install, which is | |
| # ~10s on a healthy runner. Cache key is the resolved version from | |
| # package-lock.json so a Playwright bump auto-invalidates. | |
| - name: Get Playwright version | |
| id: playwright-version | |
| working-directory: web | |
| run: echo "version=$(node -p "require('@playwright/test/package.json').version")" >> $GITHUB_OUTPUT | |
| - name: Cache Playwright browsers | |
| id: playwright-cache | |
| uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 | |
| with: | |
| path: ~/.cache/ms-playwright | |
| key: playwright-${{ runner.os }}-${{ steps.playwright-version.outputs.version }}-chromium | |
| - name: Install Playwright browsers (cache miss) | |
| if: steps.playwright-cache.outputs.cache-hit != 'true' | |
| working-directory: web | |
| # --with-deps pulls in the Ubuntu libraries Playwright needs | |
| # (libatk, libnss, libcups, …). Scoped to chromium to cut download | |
| # time — the suite's mobile project uses Pixel 7, which defaults to | |
| # Chromium, so we don't need WebKit. | |
| run: npx playwright install --with-deps chromium | |
| - name: Install Playwright system deps (cache hit) | |
| if: steps.playwright-cache.outputs.cache-hit == 'true' | |
| working-directory: web | |
| # When the browser binary cache hits, we still need the apt-level | |
| # system libraries on the fresh runner — `install-deps` does just | |
| # that without re-downloading the browser binary itself. | |
| run: npx playwright install-deps chromium | |
| - name: Run Playwright | |
| working-directory: web | |
| env: | |
| CI: "1" | |
| run: npx playwright test | |
| - name: Upload Playwright report on failure | |
| if: failure() | |
| uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 | |
| with: | |
| name: playwright-report | |
| path: web/playwright-report/ | |
| retention-days: 14 |