Skip to content

fix(ui): tolerate generic keydown events #1223

fix(ui): tolerate generic keydown events

fix(ui): tolerate generic keydown events #1223

Workflow file for this run

name: CI
on:
push:
branches:
- master
- release/*
pull_request:
paths-ignore:
- "**.md"
workflow_dispatch:
inputs:
release_type:
type: choice
options:
- Dry Run
- Release
# Runners resolve through repo/org `vars` (ArcBox pins paid Blacksmith labels there);
# forks without the vars fall back to GitHub-hosted labels with zero setup.
# Pull request and release-branch CI keeps latest-run cancellation. Every master SHA gets a unique
# group so a release-PR merge cannot be canceled or replaced before its exact SHA is tested.
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}-${{ github.ref == 'refs/heads/master' && github.sha || 'latest' }}
cancel-in-progress: ${{ github.ref != 'refs/heads/master' }}
permissions:
contents: read
env:
# Typed linting needs one TS program set in a single heap: `lint:ci` runs eslint with
# `--concurrency=off`, and a cold cache still walks the whole repo, which does not fit in 4096.
NODE_OPTIONS: --max-old-space-size=6144
jobs:
typescript:
name: TypeScript
runs-on: ${{ vars.CI_RUNNER_LINUX || 'ubuntu-latest' }}
timeout-minutes: 30
steps:
- uses: actions/checkout@v7
- name: Setup Rust
id: setup-rust
uses: actions-rust-lang/setup-rust-toolchain@v1
with:
rustflags: "" # keep build.rustflags; the -D warnings default belongs to the Rust job's clippy call
background: true
- parallel:
- uses: pnpm/action-setup@v6
with:
run_install: false
cache: true
- uses: actions/setup-node@v6
with:
node-version-file: .nvmrc
package-manager-cache: false
# Before `pnpm install`, so the hashFiles globs below cannot reach into node_modules.
#
# eslint validates a cached entry against that file's own content only, so a cross-file
# type change would leave an unchanged file's cached result in place — a type-aware rule
# such as no-floating-promises can start applying to a caller nobody edited, and `tsc`
# does not reject that code either. The key therefore covers every lint-visible input at
# once and there are deliberately no restore-keys: a prefix match would reintroduce
# exactly that hole. The cost is that this only hits on commits touching no lintable
# source (docs, Rust, workflows); a run that changes any of them re-lints from cold,
# which is the correct answer.
- name: Restore ESLint cache
uses: actions/cache@v6
with:
path: .eslintcache
key: eslint-${{ runner.os }}-${{ hashFiles('pnpm-lock.yaml', 'pnpm-workspace.yaml', 'package.json', 'apps/*/package.json', 'packages/*/*/package.json', 'tsconfig*.json', 'apps/*/tsconfig.json', 'apps/*/tests/tsconfig.json', 'apps/*/e2e/tsconfig.json', 'packages/*/*/tsconfig.json', 'packages/*/*/tests/tsconfig.json') }}-${{ hashFiles('packages/*/*/src/**') }}-${{ github.sha }}
restore-keys: |
eslint-${{ runner.os }}-${{ hashFiles('pnpm-lock.yaml', 'pnpm-workspace.yaml', 'package.json', 'apps/*/package.json', 'packages/*/*/package.json', 'tsconfig*.json', 'apps/*/tsconfig.json', 'apps/*/tests/tsconfig.json', 'apps/*/e2e/tsconfig.json', 'packages/*/*/tsconfig.json', 'packages/*/*/tests/tsconfig.json') }}-${{ hashFiles('packages/*/*/src/**') }}-
eslint-${{ runner.os }}-
- name: Install dependencies
run: pnpm install --frozen-lockfile
# After `pnpm install`, which could otherwise prune the restored node_modules/.cache dirs.
# `tsc --build` validates staleness from .tsbuildinfo content hashes, not timestamps
# (touching every source leaves a warm build sub-second), so restoring a stale build info
# is always safe — tsc rechecks exactly what changed. restore-keys is therefore sound here,
# unlike for the ESLint cache above.
- name: Restore TypeScript build info
uses: actions/cache@v6
with:
path: |
apps/*/node_modules/.cache/tsconfig*.tsbuildinfo
packages/*/*/node_modules/.cache/tsconfig*.tsbuildinfo
key: tsc-${{ runner.os }}-${{ hashFiles('pnpm-lock.yaml', 'tsconfig*.json', 'apps/*/tsconfig.json', 'apps/*/tests/tsconfig.json', 'apps/*/e2e/tsconfig.json', 'packages/*/*/tsconfig.json', 'packages/*/*/tests/tsconfig.json') }}-${{ github.sha }}
restore-keys: |
tsc-${{ runner.os }}-${{ hashFiles('pnpm-lock.yaml', 'tsconfig*.json', 'apps/*/tsconfig.json', 'apps/*/tests/tsconfig.json', 'apps/*/e2e/tsconfig.json', 'packages/*/*/tsconfig.json', 'packages/*/*/tests/tsconfig.json') }}-
tsc-${{ runner.os }}-
- wait: setup-rust
- name: Build PTY sidecar for interoperability tests
id: build-sidecar
run: cargo build --locked -p linkcode-pty
background: true
- name: Check formatting and imports
run: pnpm format:check
# `lint:ci`, not `lint`: multithread linting duplicates the typescript-eslint program per
# worker instead of splitting it, which OOMs a 2-worker runner (CODE-468).
- name: Lint
run: pnpm lint:ci
- name: Typecheck
run: pnpm typecheck
- wait: build-sidecar
- name: Test
env:
LINKCODE_PTY_SIDECAR_PATH: ${{ github.workspace }}/target/debug/linkcode-pty
LINKCODE_REQUIRE_PTY_SIDECAR: "1"
run: |
pnpm test --exclude='apps/daemon/tests/integration/**'
pnpm test apps/daemon/tests/integration --no-file-parallelism
- name: Daemon process acceptance
env:
LINKCODE_PTY_SIDECAR_PATH: ${{ github.workspace }}/target/debug/linkcode-pty
run: |
pnpm -F @linkcode/daemon build
pnpm -F @linkcode/daemon e2e:startup
desktop:
name: Desktop App Entry
runs-on: ${{ vars.CI_RUNNER_LINUX || 'ubuntu-latest' }}
timeout-minutes: 30
steps:
- uses: actions/checkout@v7
- name: Setup Rust
id: setup-rust
uses: actions-rust-lang/setup-rust-toolchain@v1
with:
rustflags: ""
background: true
- parallel:
- uses: pnpm/action-setup@v6
with:
run_install: false
cache: true
- uses: actions/setup-node@v6
with:
node-version-file: .nvmrc
package-manager-cache: false
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Install Electron system dependencies
run: |
pnpm -F @linkcode/desktop exec playwright-core install-deps chromium
sudo apt-get install --no-install-recommends -y openbox x11-utils
- name: Test unpackaged app entry
run: xvfb-run -a pnpm -F @linkcode/desktop e2e:unpackaged
- name: Test window state persistence
run: |
xvfb-run -a sh -c '
openbox >/tmp/linkcode-openbox.log 2>&1 &
wm_pid=$!
trap "kill $wm_pid 2>/dev/null || true" EXIT
# Maximize needs a managing WM; wait for the EWMH readiness marker instead
# of racing openbox startup.
for _ in $(seq 1 50); do
xprop -root _NET_SUPPORTING_WM_CHECK 2>/dev/null | grep -q "window id" && break
sleep 0.2
done
pnpm -F @linkcode/desktop e2e:window-bounds
'
- wait: setup-rust
- name: Test unsigned packaged app entry
run: xvfb-run -a pnpm -F @linkcode/desktop e2e:packaged
webview:
name: Webview Browser Entry
runs-on: ${{ vars.CI_RUNNER_LINUX || 'ubuntu-latest' }}
timeout-minutes: 20
steps:
- name: Checkout
uses: actions/checkout@v7
- parallel:
- uses: pnpm/action-setup@v6
with:
run_install: false
cache: true
- uses: actions/setup-node@v6
with:
node-version-file: .nvmrc
package-manager-cache: false
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Install Chromium
run: pnpm -F @linkcode/webview exec playwright-core install --with-deps chromium --only-shell
- name: Test production and mock browser entries
run: pnpm -F @linkcode/webview e2e:browser
# Never expose production telemetry configuration to pull-request code. The browser smoke
# above deliberately runs without it; push/manual builds separately verify the real bundle.
- name: Build production bundle with telemetry
if: ${{ github.event_name != 'pull_request' }}
env:
VITE_SENTRY_DSN: ${{ secrets.SENTRY_DSN_WEBVIEW }}
VITE_POSTHOG_PROJECT_TOKEN: ${{ secrets.POSTHOG_PROJECT_TOKEN }}
VITE_POSTHOG_HOST: ${{ secrets.POSTHOG_HOST }}
run: pnpm -F @linkcode/webview build
mobile:
name: Mobile Native Bundles
runs-on: ${{ vars.CI_RUNNER_LINUX || 'ubuntu-latest' }}
timeout-minutes: 20
steps:
- name: Checkout
uses: actions/checkout@v7
- parallel:
- uses: pnpm/action-setup@v6
with:
run_install: false
cache: true
- uses: actions/setup-node@v6
with:
node-version-file: .nvmrc
package-manager-cache: false
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Export Android and iOS app entries
# SENTRY_AUTH_TOKEN intentionally stays in EAS: ordinary CI does not upload source maps.
env:
EXPO_PUBLIC_SENTRY_DSN: ${{ github.event_name != 'pull_request' && secrets.SENTRY_DSN_MOBILE || '' }}
EXPO_PUBLIC_POSTHOG_PROJECT_TOKEN: ${{ github.event_name != 'pull_request' && secrets.POSTHOG_PROJECT_TOKEN || '' }}
EXPO_PUBLIC_POSTHOG_HOST: ${{ github.event_name != 'pull_request' && secrets.POSTHOG_HOST || '' }}
run: pnpm -F @linkcode/mobile smoke:export
rust:
name: Rust
runs-on: ${{ vars.CI_RUNNER_LINUX || 'ubuntu-latest' }}
timeout-minutes: 20
steps:
- name: Checkout
uses: actions/checkout@v7
# rustfmt and clippy come from rust-toolchain.toml's components.
- name: Setup Rust
uses: actions-rust-lang/setup-rust-toolchain@v1
with:
rustflags: ""
- parallel:
- name: Format
run: cargo fmt --check
- name: Clippy
run: cargo clippy --all-targets --locked -- -D warnings
- name: Test
run: cargo test --locked
all-green:
name: All Green
runs-on: ${{ vars.CI_RUNNER_LINUX || 'ubuntu-latest' }}
timeout-minutes: 5
needs:
- typescript
- desktop
- webview
- mobile
- rust
if: always()
steps:
- name: Check required jobs
run: |
if [ '${{ needs.typescript.result }}' != 'success' ] || [ '${{ needs.desktop.result }}' != 'success' ] || [ '${{ needs.webview.result }}' != 'success' ] || [ '${{ needs.mobile.result }}' != 'success' ] || [ '${{ needs.rust.result }}' != 'success' ]; then
exit 1
fi