Skip to content

fix(mobile): give the PR Review paste control a visible result on every tap #22977

fix(mobile): give the PR Review paste control a visible result on every tap

fix(mobile): give the PR Review paste control a visible result on every tap #22977

Workflow file for this run

name: kilo-app CI
# The mobile suite jobs are required checks on main, so this workflow runs on
# every PR and every main push (no path filters) and the `mobile-changes` job
# gates the suite work. An unrelated PR or push still reports the suite checks
# as skipped (a skipped job satisfies a required check) instead of leaving them
# "Expected". The release workflow re-runs the suite through `workflow_call`.
on:
push:
branches: [main]
pull_request:
merge_group:
workflow_call:
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
permissions:
contents: read
jobs:
# Always runs: the required suite checks must report on every PR and every
# main push. The detection step diffs against the PR's base sha or, on a main
# push, `github.event.before`; when neither is available (workflow_dispatch
# release), the suite runs (a release must validate).
mobile-changes:
runs-on: ${{ vars.RUNNER_DEFAULT_LABEL || 'ubuntu-latest' }}
timeout-minutes: 5
outputs:
mobile: ${{ steps.detect.outputs.mobile }}
steps:
# Full history so the change-detection step can diff against the PR's
# base sha or the pushed commit's parent.
- uses: useblacksmith/checkout@41cdeedae8edb2e684ba22896a5fd2a3cb85db6b # v1
with:
fetch-depth: 0
- name: Setup pnpm
uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v4.4.0
- name: Setup Node
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
with:
node-version-file: '.nvmrc'
# The suite jobs always report (required checks); only the suite's own
# paths justify the heavy work. The patterns are pinned by
# scripts/stacked-ci.test.mjs, which ci.yml's `ci-wiring` job runs.
# A lockfile change matches the patterns first, so the detector below
# never parses the lockfile and needs no installed dependencies.
- name: Detect mobile-relevant changes
id: detect
run: |
set -euo pipefail
# On a PR, diff against the base sha; on a main push (or a
# workflow_call riding a push event), diff against
# `github.event.before`. If neither object is available, run the
# suite rather than skip it: a false "no changes" would break the
# required checks' promise, while a redundant run is harmless.
# A merge queue entry diffs against the group's base: the PRs in the
# group passed the suite on their own branches.
base="${{ github.event.pull_request.base.sha || github.event.merge_group.base_sha }}"
if ! git rev-parse --verify "${base}^{commit}" > /dev/null 2>&1; then
base="${{ github.event.before }}"
fi
if ! git rev-parse --verify "${base}^{commit}" > /dev/null 2>&1; then
echo "mobile=true" >> "$GITHUB_OUTPUT"
exit 0
fi
if git diff --name-only "$base" HEAD \
| grep -Eq '^apps/mobile/|^packages/trpc/|^apps/web/src/routers/|^packages/web-shared/src/routers/|^packages/app-shared/|^packages/kilo-chat/|^packages/kilo-chat-hooks/|^packages/event-service/|^packages/notifications/|^packages/cloud-agent-sdk/|^pnpm-lock\.yaml$|^\.github/workflows/kilo-app-ci\.yml$|^\.github/workflows/ci\.yml$|^scripts/stacked-ci\.test\.mjs$'; then
echo "mobile=true" >> "$GITHUB_OUTPUT"
# The app also reads every workspace it depends on, directly or
# through a relative import, and its tests may read other
# workspaces' files as data. A detection error runs the suite.
elif ! affected=$(node scripts/affected-workspaces.mjs "$(git rev-parse "${base}^{commit}")") \
|| ! selected=$(jq -r '.full or (.packages + .test_only_packages | index("kilo-app") != null)' <<< "$affected") \
|| [ "$selected" != false ]; then
echo "mobile=true" >> "$GITHUB_OUTPUT"
else
echo "mobile=false" >> "$GITHUB_OUTPUT"
fi
typecheck:
needs: mobile-changes
if: needs.mobile-changes.outputs.mobile == 'true'
runs-on: ${{ vars.RUNNER_DEFAULT_LABEL || 'ubuntu-latest' }}
timeout-minutes: 15
steps:
- uses: useblacksmith/checkout@41cdeedae8edb2e684ba22896a5fd2a3cb85db6b # v1
- name: Setup pnpm
uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v4.4.0
- name: Mount pnpm store
uses: ./.github/actions/pnpm-store
- name: Setup Node
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
with:
node-version-file: '.nvmrc'
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Cache compiler plugins
uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4
with:
path: node_modules/.cache/ttsc/plugins
key: ${{ runner.os }}-${{ runner.arch }}-ttsc-plugins-${{ hashFiles('pnpm-lock.yaml') }}
restore-keys: |
${{ runner.os }}-${{ runner.arch }}-ttsc-plugins-
- name: Restore or build tRPC declarations
uses: ./.github/actions/trpc-declarations
- name: Build harness SDK
run: pnpm --filter @kilocode/harness-sdk run build
- name: Typecheck
run: pnpm --filter kilo-app run typecheck
# The release preflight is the only other job that evaluates app.config.ts,
# and it runs nightly. Read the config here so a broken import fails the PR.
- name: Read Expo config
run: pnpm --filter kilo-app exec expo config --type public > /dev/null
lint:
needs: mobile-changes
if: needs.mobile-changes.outputs.mobile == 'true'
runs-on: ${{ vars.RUNNER_DEFAULT_LABEL || 'ubuntu-latest' }}
timeout-minutes: 15
steps:
- uses: useblacksmith/checkout@41cdeedae8edb2e684ba22896a5fd2a3cb85db6b # v1
- name: Setup pnpm
uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v4.4.0
- name: Mount pnpm store
uses: ./.github/actions/pnpm-store
- name: Setup Node
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
with:
node-version-file: '.nvmrc'
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Cache compiler plugins
uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4
with:
path: node_modules/.cache/ttsc/plugins
key: ${{ runner.os }}-${{ runner.arch }}-ttsc-plugins-${{ hashFiles('pnpm-lock.yaml') }}
restore-keys: |
${{ runner.os }}-${{ runner.arch }}-ttsc-plugins-
- name: Restore or build tRPC declarations
uses: ./.github/actions/trpc-declarations
- name: Build harness SDK
run: pnpm --filter @kilocode/harness-sdk run build
- name: Lint
run: pnpm --filter kilo-app run lint
i18n-leftover:
needs: mobile-changes
if: needs.mobile-changes.outputs.mobile == 'true'
runs-on: ${{ vars.RUNNER_DEFAULT_LABEL || 'ubuntu-latest' }}
timeout-minutes: 15
steps:
- uses: useblacksmith/checkout@41cdeedae8edb2e684ba22896a5fd2a3cb85db6b # v1
- name: Setup pnpm
uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v4.4.0
- name: Mount pnpm store
uses: ./.github/actions/pnpm-store
- name: Setup Node
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
with:
node-version-file: '.nvmrc'
- name: Install dependencies
run: pnpm install --frozen-lockfile
# Neither check reads types or built packages: the i18n oxlint config
# is not type-aware.
- name: Check i18n catalogs and leftover copy
run: pnpm --filter kilo-app run check:i18n
- name: Check for silently-dropped NativeWind classes
run: pnpm --filter kilo-app run check:classes
- name: Test the NativeWind class checker
run: node --test tools/nativewind/check-classes.test.mjs
- name: Test the i18n catalog checker
run: node --test tools/i18n/check-catalogs.test.mjs
format-check:
needs: mobile-changes
if: needs.mobile-changes.outputs.mobile == 'true'
runs-on: ${{ vars.RUNNER_DEFAULT_LABEL || 'ubuntu-latest' }}
timeout-minutes: 15
steps:
- uses: useblacksmith/checkout@41cdeedae8edb2e684ba22896a5fd2a3cb85db6b # v1
- name: Setup pnpm
uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v4.4.0
- name: Mount pnpm store
uses: ./.github/actions/pnpm-store
- name: Setup Node
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
with:
node-version-file: '.nvmrc'
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Format check
run: |
pnpm --filter kilo-app run format:check || {
echo "::error::oxfmt formatting check failed. Run 'cd apps/mobile && pnpm format' locally and commit the changes."
exit 1
}
check-unused:
needs: mobile-changes
if: needs.mobile-changes.outputs.mobile == 'true'
runs-on: ${{ vars.RUNNER_DEFAULT_LABEL || 'ubuntu-latest' }}
timeout-minutes: 15
steps:
- uses: useblacksmith/checkout@41cdeedae8edb2e684ba22896a5fd2a3cb85db6b # v1
- name: Setup pnpm
uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v4.4.0
- name: Mount pnpm store
uses: ./.github/actions/pnpm-store
- name: Setup Node
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
with:
node-version-file: '.nvmrc'
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Cache compiler plugins
uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4
with:
path: node_modules/.cache/ttsc/plugins
key: ${{ runner.os }}-${{ runner.arch }}-ttsc-plugins-${{ hashFiles('pnpm-lock.yaml') }}
restore-keys: |
${{ runner.os }}-${{ runner.arch }}-ttsc-plugins-
- name: Build harness SDK
run: pnpm --filter @kilocode/harness-sdk run build
- name: Check unused exports
run: pnpm --filter kilo-app run check:unused
# Eight shards run the mobile Vitest suite in parallel; the `test` job below
# reports the combined result under the required check name.
#
# The shard's `Test` step is CPU-bound and the jobs start together, so the
# runner size sets the wall clock of the whole row: measured on a work VM the
# step drops from 84 s to 36 s per shard (2 vCPU -> 4 vCPU), against a ~15 s
# fixed setup that does not change. Four vCPU pays for itself: the mobile row
# gets shorter while its vCPU-seconds rise by about 2%.
#
# The label is `vars.RUNNER_MEDIUM_LABEL` (blacksmith-4vcpu-ubuntu-2404, 4 vCPU
# and 16 GB; a shard peaks at 0.6 GB). Repo variables are not exposed to fork
# pull requests, so the job falls back to `ubuntu-latest` there.
mobile-test:
needs: mobile-changes
if: needs.mobile-changes.outputs.mobile == 'true'
name: mobile-test (${{ matrix.shard }}/8)
strategy:
fail-fast: false
matrix:
shard: [1, 2, 3, 4, 5, 6, 7, 8]
runs-on: ${{ vars.RUNNER_MEDIUM_LABEL || 'ubuntu-latest' }}
timeout-minutes: 20
steps:
- uses: useblacksmith/checkout@41cdeedae8edb2e684ba22896a5fd2a3cb85db6b # v1
- name: Setup pnpm
uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v4.4.0
- name: Mount pnpm store
uses: ./.github/actions/pnpm-store
- name: Setup Node
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
with:
node-version-file: '.nvmrc'
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Cache compiler plugins
uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4
with:
path: node_modules/.cache/ttsc/plugins
key: ${{ runner.os }}-${{ runner.arch }}-ttsc-plugins-${{ hashFiles('pnpm-lock.yaml') }}
restore-keys: |
${{ runner.os }}-${{ runner.arch }}-ttsc-plugins-
# The tests import the harness SDK build at run time. The tRPC build
# holds only type declarations, which Vitest does not read.
- name: Build harness SDK
run: pnpm --filter @kilocode/harness-sdk run build
- name: Test
run: pnpm --filter kilo-app run test --shard=${{ matrix.shard }}/8
# Required check. Skipped shards (no mobile change) pass; a failed or
# cancelled shard fails this job.
test:
needs: [mobile-changes, mobile-test]
if: ${{ !cancelled() }}
runs-on: ${{ vars.RUNNER_DEFAULT_LABEL || 'ubuntu-latest' }}
timeout-minutes: 5
steps:
- name: Check test results
env:
RESULTS: ${{ toJSON(needs.*.result) }}
run: |
echo "$RESULTS"
if jq -e 'any(.[]; . == "failure" or . == "cancelled")' <<< "$RESULTS" > /dev/null; then
exit 1
fi