diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c3c286f30a8c..0307924fd56b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -827,10 +827,6 @@ jobs: NODE_OPTIONS: --max-old-space-size=8192 run: pnpm build:ci-artifacts - - name: Check Control UI i18n - if: needs.preflight.outputs.run_control_ui_i18n == 'true' - run: pnpm ui:i18n:check - - name: Pack built runtime artifacts run: tar --posix -cf dist-runtime-build.tar.zst --use-compress-program zstdmt dist dist-runtime packages/*/dist @@ -1065,6 +1061,28 @@ jobs: pnpm --dir ui test fi + control-ui-i18n: + permissions: + contents: read + name: control-ui-i18n + needs: [preflight] + if: needs.preflight.outputs.run_control_ui_i18n == 'true' + # Locale refresh runs separately on main and nightly. Keep drift visible on + # automatic runs without blocking unrelated work; release CI stays strict. + continue-on-error: ${{ github.event_name != 'workflow_dispatch' }} + runs-on: ${{ github.event_name == 'workflow_dispatch' && 'ubuntu-24.04' || (github.repository == 'openclaw/openclaw' && 'blacksmith-4vcpu-ubuntu-2404' || 'ubuntu-24.04') }} + timeout-minutes: 10 + steps: + - *linux_node_checkout_step + - name: Setup Node environment + uses: ./.github/actions/setup-node-env + with: + node-version: "24.x" + install-bun: "false" + + - name: Check Control UI locale parity + run: pnpm ui:i18n:check + checks-fast-core: permissions: contents: read @@ -1677,7 +1695,6 @@ jobs: env: ADDITIONAL_CHECK_GROUP: ${{ matrix.group }} OPENCLAW_ADDITIONAL_BOUNDARY_SHARD: ${{ matrix.boundary_shard || '' }} - RUN_CONTROL_UI_I18N: ${{ needs.preflight.outputs.run_control_ui_i18n }} OPENCLAW_ADDITIONAL_BOUNDARY_CONCURRENCY: 4 OPENCLAW_EXTENSION_BOUNDARY_CONCURRENCY: 6 shell: bash @@ -2461,6 +2478,7 @@ jobs: - pnpm-store-warmup - build-artifacts - checks-ui + - control-ui-i18n - checks-fast-core - checks-fast-plugin-contracts-shard - checks-fast-channel-contracts-shard diff --git a/docs/ci.md b/docs/ci.md index fe7fdf260a4c..b7c0501da399 100644 --- a/docs/ci.md +++ b/docs/ci.md @@ -32,6 +32,7 @@ dispatch. | `security-fast` | Private key detection, changed-workflow audit via `zizmor`, and production lockfile audit | Always on non-draft pushes and PRs | | `pnpm-store-warmup` | Warm the lockfile-pinned pnpm store cache without blocking Linux Node shards | Node or docs-check lanes selected | | `build-artifacts` | Build `dist/`, Control UI, built-CLI smoke checks, startup memory, and embedded built-artifact checks | Node-relevant changes | +| `control-ui-i18n` | Verify generated Control UI locale bundles, metadata, and translation memory; advisory on automatic runs, blocking on manual release CI | Control UI i18n-relevant changes and manual CI | | `checks-fast-core` | Fast Linux correctness lanes: bundled + protocol, Bun launcher, and the CI-routing fast task | Node-relevant changes | | `qa-smoke-ci-profile` | Two self-contained balanced parts of the bounded automatic QA Smoke representative set; full taxonomy coverage remains available through explicit QA profiles | Node-relevant changes | | `checks-fast-contracts-plugins-*` | Two weighted plugin contract shards | Node-relevant changes | @@ -56,7 +57,7 @@ dispatch. 1. `runner-admission` waits only for canonical `main` pushes; a newer push cancels the run before Blacksmith registration. 2. `preflight` decides which lanes exist at all. The `docs-scope` and `changed-scope` logic are steps inside this job, not standalone jobs. 3. `security-fast`, `check-*`, `check-additional-*`, `check-docs`, and `skills-python` fail quickly without waiting on the heavier artifact and platform matrix jobs. -4. `build-artifacts` overlaps with the fast Linux lanes so downstream consumers can start as soon as the shared build is ready. +4. `build-artifacts` and the advisory `control-ui-i18n` check overlap with the fast Linux lanes. Generated locale drift stays visible while the standalone refresh workflow repairs it in the background. 5. Heavier platform and runtime lanes fan out after that: `checks-fast-core`, `checks-fast-contracts-plugins-*`, `checks-fast-contracts-channels-*`, `checks-node-*`, `checks-windows`, `macos-node`, `macos-swift`, `ios-build`, and `android`. GitHub may mark superseded jobs as `cancelled` when a newer push lands on the same PR or `main` ref. Treat that as CI noise unless the newest run for the same ref is also failing. Matrix jobs use `fail-fast: false`, and `build-artifacts` reports embedded channel, core-support-boundary, and gateway-watch failures directly instead of queuing tiny verifier jobs. The automatic CI concurrency key is versioned (`CI-v7-*`) so a GitHub-side zombie in an old queue group cannot indefinitely block newer main runs. Manual full-suite runs use `CI-manual-v1-*` and do not cancel in-progress runs. The plugin-list startup-memory guard keeps a 350 MiB ceiling on self-hosted Blacksmith Linux and allows 425 MiB on GitHub-hosted Linux, whose RSS baseline is higher for the same built CLI. @@ -131,7 +132,7 @@ Treat GitHub titles, comments, bodies, review text, branch names, and commit mes ## Manual dispatches -Manual CI dispatches run the same job graph as normal CI but force every non-Android scoped lane on: Linux Node shards, bundled-plugin shards, plugin and channel contract shards, Node 22 compatibility, `check-*`, `check-additional-*`, built-artifact smoke checks, docs checks, Python skills, Windows, macOS, iOS build, and Control UI i18n. Standalone manual CI dispatches run Android only with `include_android=true` (the `release_gate` input also forces Android); the full release umbrella enables Android by passing `include_android=true`. Plugin prerelease static checks, the release-only `agentic-plugins` shard, the full extension batch sweep, and plugin prerelease Docker lanes are excluded from CI. The Docker prerelease suite runs only when `Full Release Validation` dispatches the separate `Plugin Prerelease` workflow with the release-validation gate enabled. +Manual CI dispatches run the same job graph as normal CI but force every non-Android scoped lane on: Linux Node shards, bundled-plugin shards, plugin and channel contract shards, Node 22 compatibility, `check-*`, `check-additional-*`, built-artifact smoke checks, docs checks, Python skills, Windows, macOS, iOS build, and Control UI i18n. Control UI locale parity is advisory on automatic PR and `main` runs because the standalone refresh workflow repairs generated drift in the background; it is blocking on manual CI and therefore on Full Release Validation. Standalone manual CI dispatches run Android only with `include_android=true` (the `release_gate` input also forces Android); the full release umbrella enables Android by passing `include_android=true`. Plugin prerelease static checks, the release-only `agentic-plugins` shard, the full extension batch sweep, and plugin prerelease Docker lanes are excluded from CI. The Docker prerelease suite runs only when `Full Release Validation` dispatches the separate `Plugin Prerelease` workflow with the release-validation gate enabled. Manual runs use a unique concurrency group so a release-candidate full suite is not cancelled by another push or PR run on the same ref. The optional `target_ref` input lets a trusted caller run that graph against a branch, tag, or full commit SHA while using the workflow file from the selected dispatch ref. The `release_gate` input is an exact-SHA maintainer fallback for capacity-stalled PR CI: it requires `target_ref` to be a full commit SHA that matches the dispatched branch head. @@ -196,6 +197,8 @@ pnpm test:gateway:watch-regression OPENCLAW_TUI_PTY_INCLUDE_LOCAL=1 node scripts/run-vitest.mjs run --config test/vitest/vitest.tui-pty.config.ts pnpm test # vitest tests pnpm test:changed # cheap smart changed Vitest targets +pnpm test:ui # Control UI unit/browser suite +pnpm ui:i18n:check # generated Control UI locale parity (release gate) pnpm test:channels pnpm test:contracts:channels pnpm check:docs # docs format + lint + broken links diff --git a/package.json b/package.json index a862bcd88756..9de2e9fcbfab 100644 --- a/package.json +++ b/package.json @@ -1952,7 +1952,7 @@ "test:startup:gateway": "node --import tsx scripts/bench-gateway-startup.ts", "test:restart:gateway": "node --import tsx scripts/bench-gateway-restart.ts", "test:startup:memory": "node scripts/ensure-cli-startup-build.mjs && node scripts/check-cli-startup-memory.mjs", - "test:ui": "pnpm ui:i18n:check && pnpm lint:ui:no-raw-window-open && node scripts/ensure-playwright-chromium.mjs && pnpm --dir ui test", + "test:ui": "pnpm lint:ui:no-raw-window-open && node scripts/ensure-playwright-chromium.mjs && pnpm --dir ui test", "test:ui:e2e": "node scripts/ensure-playwright-chromium.mjs && node scripts/run-vitest.mjs run --config test/vitest/vitest.ui-e2e.config.ts --configLoader runner", "test:unit": "pnpm test:unit:fast && node scripts/run-vitest.mjs run --config test/vitest/vitest.unit.config.ts", "test:unit:fast": "node scripts/run-vitest.mjs run --config test/vitest/vitest.unit-fast.config.ts", diff --git a/test/scripts/ci-workflow-guards.test.ts b/test/scripts/ci-workflow-guards.test.ts index ac7589ae281e..78d1418e53a3 100644 --- a/test/scripts/ci-workflow-guards.test.ts +++ b/test/scripts/ci-workflow-guards.test.ts @@ -1915,6 +1915,34 @@ describe("ci workflow guards", () => { ); }); + it("keeps Control UI locale parity advisory until release CI", () => { + const workflow = readCiWorkflow(); + const workflowSource = readFileSync(".github/workflows/ci.yml", "utf8"); + const buildArtifactSteps = workflow.jobs["build-artifacts"].steps; + const localeJob = workflow.jobs["control-ui-i18n"]; + const localeStep = localeJob.steps.find( + (step: WorkflowStep) => step.name === "Check Control UI locale parity", + ); + + expect(buildArtifactSteps).not.toContainEqual( + expect.objectContaining({ run: "pnpm ui:i18n:check" }), + ); + expect(JSON.parse(readFileSync("package.json", "utf8")).scripts["test:ui"]).not.toContain( + "ui:i18n:check", + ); + expect(workflowSource.match(/pnpm ui:i18n:check/gu)).toHaveLength(1); + expect(readFileSync("ui/src/i18n/test/translate.test.ts", "utf8")).not.toContain( + "keeps shipped locales structurally aligned with English", + ); + expect(localeJob.needs).toEqual(["preflight"]); + expect(localeJob.if).toBe("needs.preflight.outputs.run_control_ui_i18n == 'true'"); + expect(localeJob["continue-on-error"]).toBe("${{ github.event_name != 'workflow_dispatch' }}"); + expect(localeStep.run).toBe("pnpm ui:i18n:check"); + expect(readFileSync(".github/workflows/full-release-validation.yml", "utf8")).toContain( + 'dispatch_and_wait ci.yml "$dispatch_run_name"', + ); + }); + it("keeps the hosted plugin-list memory allowance scoped to GitHub-hosted runners", () => { const workflow = readCiWorkflow(); const startupMemoryStep = workflow.jobs["build-artifacts"].steps.find( @@ -2077,6 +2105,7 @@ describe("ci workflow guards", () => { "pnpm-store-warmup", "build-artifacts", "checks-ui", + "control-ui-i18n", "checks-fast-core", "checks-fast-plugin-contracts-shard", "checks-fast-channel-contracts-shard", diff --git a/ui/src/i18n/test/translate.test.ts b/ui/src/i18n/test/translate.test.ts index d795a03948c8..3b65dfcf7ac6 100644 --- a/ui/src/i18n/test/translate.test.ts +++ b/ui/src/i18n/test/translate.test.ts @@ -227,13 +227,4 @@ describe("i18n", () => { } } }); - - it("keeps shipped locales structurally aligned with English", () => { - const englishKeys = flatten(en); - for (const [locale, value] of Object.entries(shippedLocales)) { - expect(flatten(value as Record>), locale).toEqual( - englishKeys, - ); - } - }); });