minds: design system — tokens + type / radius / spacing / elevation scales #13709
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: ['**'] | |
| jobs: | |
| # Changelog gate. Runs on the orchestrator (not in an offload | |
| # sandbox) because it diffs the PR branch against its real base branch, and | |
| # the sandbox has no base ref -- doing the diff there passes vacuously. Pure | |
| # stdlib, so no `uv sync`; just enough git history to compute the diff. | |
| check-changelog: | |
| runs-on: ubuntu-latest | |
| if: github.event_name == 'pull_request' | |
| steps: | |
| # Shallow checkout, then deepen ONLY HEAD's ancestry (same rationale as | |
| # the offload jobs below: a bare `--unshallow` pulls every branch). The | |
| # merge-base with the base branch is an ancestor of HEAD, so deepening | |
| # HEAD plus fetching the base tip is enough for a three-dot diff. | |
| - uses: actions/checkout@v6 | |
| - name: Fetch base branch and deepen HEAD for the changelog diff | |
| run: | | |
| if [ "$(git rev-parse --is-shallow-repository)" = "true" ]; then | |
| git fetch --no-tags --unshallow origin "$(git rev-parse HEAD)" | |
| fi | |
| git fetch --no-tags origin \ | |
| "+refs/heads/${GITHUB_BASE_REF}:refs/remotes/origin/${GITHUB_BASE_REF}" | |
| - name: Set up Python | |
| uses: actions/setup-python@v6 | |
| with: | |
| python-version: '3.12' | |
| - name: Check changelog entries | |
| run: python -m scripts.check_changelog_entries | |
| # Unit + integration tests via offload on Modal | |
| test-offload: | |
| runs-on: ubuntu-latest | |
| # `checks: write` is required by the report-flaky-aware-tests composite | |
| # action: both its `mikepenz/action-junit-report@v6` invocations and its | |
| # `gh api POST /repos/.../check-runs` call need it. `contents: write` is | |
| # required so offload can `git push` its image-cache git notes back to | |
| # `refs/notes/offload-images`; without it, every run is a cache miss and | |
| # rebuilds the base image from scratch (~150 s wasted per run). | |
| # Declaring permissions explicitly avoids depending on the repo-level | |
| # default, which may be restricted (contents: read only). | |
| permissions: | |
| contents: write | |
| checks: write | |
| env: | |
| MODAL_TOKEN_ID: ${{ vars.MODAL_TOKEN_ID }} | |
| MODAL_TOKEN_SECRET: ${{ secrets.MODAL_TOKEN_SECRET }} | |
| steps: | |
| # Default shallow single-ref checkout, then deepen only THIS ref's | |
| # history in the next step. offload needs the full ancestry of HEAD | |
| # (to find its checkpoint commit and thin-diff against it), but not | |
| # every other branch -- which `fetch-depth: 0` would also fetch, and on | |
| # a repo with many branches that full-history-of-all-branches fetch can | |
| # take minutes. The image-cache git notes are fetched separately below. | |
| - uses: actions/checkout@v6 | |
| - name: Deepen current-branch history for offload | |
| # Deepen ONLY the checked-out commit's ancestry -- all offload needs to | |
| # find and thin-diff its checkpoint. Naming the SHA matters: a bare | |
| # `git fetch --unshallow origin` uses the broad refspec and pulls every | |
| # branch's full history (including a heavy orphan branch), which is most | |
| # of the fetch cost. GitHub allows fetching reachable SHAs, and the | |
| # image-cache git notes are fetched separately below. | |
| run: | | |
| if [ "$(git rev-parse --is-shallow-repository)" = "true" ]; then | |
| git fetch --no-tags --unshallow origin "$(git rev-parse HEAD)" | |
| fi | |
| - name: Set up Python | |
| uses: actions/setup-python@v6 | |
| with: | |
| python-version: '3.12' | |
| - name: Install uv | |
| uses: astral-sh/setup-uv@v7 | |
| with: | |
| version: "latest" | |
| - name: Install Python dependencies | |
| run: uv sync --all-packages | |
| - name: Install just | |
| uses: extractions/setup-just@v4 | |
| - name: Install Rust toolchain | |
| uses: dtolnay/rust-toolchain@stable | |
| - name: Cache offload binary | |
| uses: actions/cache@v5 | |
| with: | |
| path: | | |
| ~/.cargo/registry | |
| ~/.cargo/git | |
| ~/.cargo/bin/offload | |
| key: cargo-offload-0.9.7-${{ runner.os }} | |
| - name: Install offload | |
| run: | | |
| if ! command -v offload &> /dev/null || ! offload --version | grep -q '0.9.7'; then | |
| cargo install offload@0.9.7 --force | |
| fi | |
| - name: Restore offload test durations from previous run | |
| uses: actions/cache/restore@v5 | |
| with: | |
| path: test-results/junit.xml | |
| key: offload-durations-${{ github.ref }}-${{ github.run_id }} | |
| restore-keys: offload-durations-${{ github.ref }}- | |
| - name: Fetch offload image-cache git notes | |
| run: git fetch origin 'refs/notes/*:refs/notes/*' || true | |
| - name: Configure git identity for image-cache note writes | |
| run: | | |
| git config user.email "dev@imbue.com" | |
| git config user.name "imbue-dev" | |
| - name: Run tests via offload | |
| id: tests | |
| run: just test-offload | |
| - name: Save offload test durations for next run | |
| if: always() | |
| uses: actions/cache/save@v5 | |
| with: | |
| path: test-results/junit.xml | |
| key: offload-durations-${{ github.ref }}-${{ github.run_id }} | |
| - name: Combine coverage and check per-package thresholds | |
| if: always() | |
| run: | | |
| set -u | |
| # Diagnostic: correlate per-sandbox coverage delivery vs per-sandbox junit. | |
| # Drift in per-package coverage across runs (with zero test failures) most | |
| # likely means some sandbox dropped its .coverage file while still | |
| # reporting tests via junit. If these counts differ, that's the cause. | |
| # Extract unique sandbox IDs from both the per-sandbox junit files | |
| # (sb-<id>.xml, exactly one per sandbox) and the per-batch .coverage | |
| # files (test-results/<sandbox-id>/<batch>/.coverage, one per batch | |
| # within a sandbox, typically multiple per sandbox). Compare on | |
| # UNIQUE-SANDBOX counts so the MISMATCH check is apples-to-apples. | |
| # Raw file counts are kept as additional info but are not directly | |
| # comparable (coverage files are per-batch, junit files are | |
| # per-sandbox). | |
| cov_file_count=$(find test-results/ -name ".coverage" -type f 2>/dev/null | wc -l | tr -d ' ') | |
| junit_file_count=$(find test-results/junit-parts/ -name "sb-*.xml" 2>/dev/null | wc -l | tr -d ' ') | |
| junit_ids=$(find test-results/junit-parts/ -name "sb-*.xml" 2>/dev/null \ | |
| | sed -E 's|.*/(sb-[^/]+)\.xml$|\1|' | sort -u) | |
| cov_ids=$(find test-results/ -mindepth 2 -name ".coverage" -type f 2>/dev/null \ | |
| | sed -E 's|^test-results/([^/]+)/.*|\1|' | sort -u) | |
| junit_sandbox_count=$(printf '%s\n' "$junit_ids" | awk 'NF' | wc -l | tr -d ' ') | |
| cov_sandbox_count=$(printf '%s\n' "$cov_ids" | awk 'NF' | wc -l | tr -d ' ') | |
| echo "=== Coverage-data delivery diagnostic ===" | |
| echo " sandboxes with .coverage: $cov_sandbox_count" | |
| echo " sandboxes with junit: $junit_sandbox_count" | |
| echo " total .coverage files (per-batch): $cov_file_count" | |
| echo " total junit files (per-sandbox): $junit_file_count" | |
| if [ "$cov_sandbox_count" != "$junit_sandbox_count" ]; then | |
| if [ "$junit_sandbox_count" -gt "$cov_sandbox_count" ]; then | |
| echo " MISMATCH: $(( junit_sandbox_count - cov_sandbox_count )) sandboxes delivered junit but not .coverage" | |
| else | |
| echo " MISMATCH: $(( cov_sandbox_count - junit_sandbox_count )) sandboxes delivered .coverage but not junit" | |
| fi | |
| echo " sandboxes with junit but no .coverage:" | |
| comm -23 <(printf '%s\n' "$junit_ids") <(printf '%s\n' "$cov_ids") | awk 'NF{print " " $0}' | |
| echo " sandboxes with .coverage but no junit:" | |
| comm -13 <(printf '%s\n' "$junit_ids") <(printf '%s\n' "$cov_ids") | awk 'NF{print " " $0}' | |
| fi | |
| i=0; for f in $(find test-results/ -name ".coverage"); do cp "$f" ".coverage.$i"; i=$((i+1)); done | |
| uv run coverage combine .coverage.* | |
| FAILED_PKGS="" | |
| for pkg in libs/* apps/*; do | |
| [ -f "$pkg/pyproject.toml" ] || continue | |
| THRESHOLD=$(python3 -c "import tomllib,sys; d=tomllib.load(open(sys.argv[1],'rb')); n=d.get('tool',{}).get('coverage',{}).get('report',{}).get('fail_under'); print('' if n is None else n)" "$pkg/pyproject.toml") | |
| [ -n "$THRESHOLD" ] || continue | |
| echo "=== $pkg (fail_under=${THRESHOLD}%) ===" | |
| if ! uv run coverage report --include="$pkg/*" --fail-under="$THRESHOLD"; then | |
| FAILED_PKGS="$FAILED_PKGS $pkg" | |
| fi | |
| done | |
| # Global backstop against regression of the combined gate. | |
| # Packages with their own much-lower fail_under (and minds, which | |
| # has its own coverage config) are excluded from the combined | |
| # report via libs/mngr/pyproject.toml's [tool.coverage.report].omit | |
| # so this 80% gate reflects the "main" package set. | |
| echo "=== combined (fail_under=80%) ===" | |
| uv run coverage report --rcfile=libs/mngr/pyproject.toml --fail-under=80 || FAILED_PKGS="$FAILED_PKGS [combined]" | |
| if [ -n "$FAILED_PKGS" ]; then | |
| echo "" | |
| echo "FAILED coverage gates:$FAILED_PKGS" | |
| exit 1 | |
| fi | |
| - name: Report test results | |
| if: always() | |
| uses: ./.github/actions/report-flaky-aware-tests | |
| with: | |
| check-name: Unit + Integration Tests | |
| junit-path: test-results/junit.xml | |
| tests-outcome: ${{ steps.tests.outcome }} | |
| github-token: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Upload test results | |
| if: always() | |
| uses: actions/upload-artifact@v7 | |
| with: | |
| name: test-results-offload | |
| path: test-results/ | |
| # Offload pulls per-sandbox files into test-results/<sb>/.test_output/, | |
| # which holds the flaky_tests_*.txt manifests used by the summary check. | |
| # Default upload-artifact behavior strips hidden paths, making post-hoc | |
| # debugging of the @flaky column impossible. | |
| include-hidden-files: true | |
| retention-days: 30 | |
| # Clean up old Modal test environments left behind by acceptance tests. | |
| cleanup-modal-environments: | |
| runs-on: ubuntu-latest | |
| env: | |
| MODAL_TOKEN_ID: ${{ vars.MODAL_TOKEN_ID }} | |
| MODAL_TOKEN_SECRET: ${{ secrets.MODAL_TOKEN_SECRET }} | |
| steps: | |
| - uses: actions/checkout@v6 | |
| - name: Set up Python | |
| uses: actions/setup-python@v6 | |
| with: | |
| python-version: '3.12' | |
| - name: Install uv | |
| uses: astral-sh/setup-uv@v7 | |
| with: | |
| version: "latest" | |
| - name: Install Python dependencies | |
| run: uv sync --all-packages | |
| - name: Clean up old Modal test environments | |
| run: uv run python scripts/cleanup_old_modal_test_environments.py --max-age-hours 1.0 | |
| # Acceptance tests via offload on Modal | |
| test-offload-acceptance: | |
| runs-on: ubuntu-latest | |
| # See note on `test-offload` above: `checks: write` is required by the | |
| # report-flaky-aware-tests composite action, and `contents: write` is | |
| # required so offload can push its image-cache git notes back to | |
| # `refs/notes/offload-images` (otherwise every run is a cache miss). | |
| permissions: | |
| contents: write | |
| checks: write | |
| env: | |
| MODAL_TOKEN_ID: ${{ vars.MODAL_TOKEN_ID }} | |
| MODAL_TOKEN_SECRET: ${{ secrets.MODAL_TOKEN_SECRET }} | |
| steps: | |
| # Default shallow single-ref checkout, then deepen only THIS ref's | |
| # history in the next step. offload needs the full ancestry of HEAD | |
| # (to find its checkpoint commit and thin-diff against it), but not | |
| # every other branch -- which `fetch-depth: 0` would also fetch, and on | |
| # a repo with many branches that full-history-of-all-branches fetch can | |
| # take minutes. The image-cache git notes are fetched separately below. | |
| - uses: actions/checkout@v6 | |
| - name: Deepen current-branch history for offload | |
| # Deepen ONLY the checked-out commit's ancestry -- all offload needs to | |
| # find and thin-diff its checkpoint. Naming the SHA matters: a bare | |
| # `git fetch --unshallow origin` uses the broad refspec and pulls every | |
| # branch's full history (including a heavy orphan branch), which is most | |
| # of the fetch cost. GitHub allows fetching reachable SHAs, and the | |
| # image-cache git notes are fetched separately below. | |
| run: | | |
| if [ "$(git rev-parse --is-shallow-repository)" = "true" ]; then | |
| git fetch --no-tags --unshallow origin "$(git rev-parse HEAD)" | |
| fi | |
| - name: Set up Python | |
| uses: actions/setup-python@v6 | |
| with: | |
| python-version: '3.12' | |
| - name: Install uv | |
| uses: astral-sh/setup-uv@v7 | |
| with: | |
| version: "latest" | |
| - name: Install Python dependencies | |
| run: uv sync --all-packages | |
| - name: Install just | |
| uses: extractions/setup-just@v4 | |
| - name: Install Rust toolchain | |
| uses: dtolnay/rust-toolchain@stable | |
| - name: Cache offload binary | |
| uses: actions/cache@v5 | |
| with: | |
| path: | | |
| ~/.cargo/registry | |
| ~/.cargo/git | |
| ~/.cargo/bin/offload | |
| key: cargo-offload-0.9.7-${{ runner.os }} | |
| - name: Install offload | |
| run: | | |
| if ! command -v offload &> /dev/null || ! offload --version | grep -q '0.9.7'; then | |
| cargo install offload@0.9.7 --force | |
| fi | |
| - name: Restore offload test durations from previous run | |
| uses: actions/cache/restore@v5 | |
| with: | |
| path: test-results/junit.xml | |
| key: offload-acceptance-durations-${{ github.ref }}-${{ github.run_id }} | |
| restore-keys: offload-acceptance-durations-${{ github.ref }}- | |
| - name: Fetch offload image-cache git notes | |
| run: git fetch origin 'refs/notes/*:refs/notes/*' || true | |
| - name: Configure git identity for image-cache note writes | |
| run: | | |
| git config user.email "dev@imbue.com" | |
| git config user.name "imbue-dev" | |
| - name: Run acceptance tests via offload | |
| id: tests | |
| run: just test-offload-acceptance | |
| - name: Save offload test durations for next run | |
| if: always() | |
| uses: actions/cache/save@v5 | |
| with: | |
| path: test-results/junit.xml | |
| key: offload-acceptance-durations-${{ github.ref }}-${{ github.run_id }} | |
| - name: Report test results | |
| if: always() | |
| uses: ./.github/actions/report-flaky-aware-tests | |
| with: | |
| check-name: Acceptance Tests | |
| junit-path: test-results/junit.xml | |
| tests-outcome: ${{ steps.tests.outcome }} | |
| github-token: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Upload test results | |
| if: always() | |
| uses: actions/upload-artifact@v7 | |
| with: | |
| name: test-results-offload-acceptance | |
| path: test-results/ | |
| # Same rationale as the test-offload upload step above: offload's | |
| # per-sandbox flaky_tests_*.txt manifests live under hidden | |
| # test-results/<sb>/.test_output/ subdirs. | |
| include-hidden-files: true | |
| retention-days: 30 | |
| # Docker acceptance tests - runs on a GitHub runner with a real Docker daemon. | |
| # Temporary: these will move to offload once Docker-in-Docker support lands. | |
| test-docker: | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v6 | |
| - name: Set up Python | |
| uses: actions/setup-python@v6 | |
| with: | |
| python-version: '3.12' | |
| - name: Install system dependencies | |
| run: | | |
| sudo apt-get update | |
| sudo apt-get install -y tmux git openssh-server unison | |
| - name: Configure Docker Hub mirror | |
| run: | | |
| echo '{"registry-mirrors": ["https://mirror.gcr.io"]}' | sudo tee /etc/docker/daemon.json | |
| sudo systemctl restart docker | |
| - name: Install uv | |
| uses: astral-sh/setup-uv@v7 | |
| with: | |
| version: "latest" | |
| - name: Install Python dependencies | |
| run: uv sync --all-packages | |
| - name: Run Docker acceptance tests | |
| # Excludes `minds_electron` so the heavyweight Electron + docker | |
| # spin-up (apps/minds/test_desktop_client_e2e.py) does not serialize | |
| # behind every other docker-marked test in this batch. That test | |
| # gets its own `test-docker-electron` job below. | |
| # Excludes `minds_snapshot_resume` because those tests only make | |
| # sense inside a Modal sandbox booted from a pre-built snapshot | |
| # (see scripts/snapshot_minds_e2e_state.py); they assume a | |
| # workspace Docker container is already in /var/lib/docker, which | |
| # is not the case on a fresh GitHub runner. Runs via | |
| # `just test-offload-minds-snapshot <image-id>` instead. | |
| run: PYTEST_MAX_DURATION_SECONDS=600 uv run pytest -n 0 --timeout 300 --no-cov --cov-fail-under=0 -v --tb=short -m '(docker or docker_sdk) and not release and not minds_electron and not minds_snapshot_resume' | |
| # Sibling to `test-docker` for the single minds Electron e2e test. Split | |
| # out because the test (~2 min end-to-end: docker pull + container start | |
| # + bootstrap + system_interface render) is the longest single | |
| # docker-marked test and would otherwise stretch the test-docker job's | |
| # wall clock. Splitting lets the two jobs run in parallel; the overall | |
| # test stage's wall clock becomes `max(test-docker, test-docker-electron)` | |
| # instead of their sum. Also avoids pulling Node / pnpm / Electron native | |
| # deps / xvfb into the test-docker runner where they would otherwise | |
| # only serve this one test. | |
| test-docker-electron: | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v6 | |
| - name: Set up Python | |
| uses: actions/setup-python@v6 | |
| with: | |
| python-version: '3.12' | |
| - name: Install system dependencies | |
| run: | | |
| sudo apt-get update | |
| sudo apt-get install -y tmux git openssh-server unison xvfb | |
| - name: Set up Node.js for Electron e2e test | |
| uses: actions/setup-node@v6 | |
| with: | |
| node-version: '24.15.0' | |
| - name: Install pnpm | |
| run: npm install --global pnpm@10.33.4 | |
| - name: Install Electron + minds JS deps | |
| run: pnpm install --dir apps/minds --frozen-lockfile | |
| - name: Configure Docker Hub mirror | |
| run: | | |
| echo '{"registry-mirrors": ["https://mirror.gcr.io"]}' | sudo tee /etc/docker/daemon.json | |
| sudo systemctl restart docker | |
| - name: Install uv | |
| uses: astral-sh/setup-uv@v7 | |
| with: | |
| version: "latest" | |
| - name: Install Python dependencies | |
| run: uv sync --all-packages | |
| - name: Run minds Electron e2e test | |
| # `xvfb-run -a` provides the display server the Electron renderer | |
| # needs; `env` sets PYTEST_MAX_DURATION_SECONDS for the wrapped | |
| # command. Both the env-var suite-level budget and the | |
| # `--timeout` per-test default match the test's own | |
| # `@pytest.mark.timeout(900)`, so the suite budget cannot kill | |
| # the pytest run before the per-test marker fires. | |
| run: xvfb-run -a env PYTEST_MAX_DURATION_SECONDS=900 uv run pytest -n 0 --timeout 900 --no-cov --cov-fail-under=0 -v --tb=short -m 'minds_electron and not release' |