Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
92 changes: 71 additions & 21 deletions .github/actions/headless-host/action.yml
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
name: Headless browser host
description: >
Build chuzz-headless and install ps-qa, so a site's built output can be driven
through a real browser engine with no window.
Download the released chuzz-headless browser and install ps-qa, so a site's
built output can be driven through a real browser engine with no window.

inputs:
chuzz-ref:
description: The chuzz revision to build the host from.
description: The chuzz revision to build only when no released binary exists.
default: master
chuzz-version:
description: >
Released browser version. Empty reads the version from the action's own
Cargo.toml, keeping callers on the binary released with this action.
default: ""
token:
description: >
A token that can read pathscale/chuzz. Optional: chuzz is public, so the
Expand All @@ -22,24 +27,65 @@ inputs:
default: ""
ps-qa-version:
description: >
The driver's version requirement. A floor rather than "latest": a check
file can use a field an older driver does not know, and the failure then
reads as a broken workflow rather than a driver that is too old.

0.7.1 uses the shared control protocol and rejects checks that silently
inherit another file's page. Keep the driver on the same protocol line
as the headless host.
default: "^0.7.1"
The driver's compatible protocol line. Cargo installs the newest
published 0.7 release, keeping the driver and headless host on the same
protocol line without pinning a patch release.
default: "^0.7"

outputs:
host:
description: Path to the built chuzz-headless binary.
value: ${{ steps.build.outputs.host }}
description: Path to the verified chuzz-headless binary.
value: ${{ steps.acquire.outputs.host }}

runs:
using: composite
steps:
- name: Check out chuzz
- name: Acquire released browser
id: acquire
shell: bash
env:
REQUESTED_VERSION: ${{ inputs.chuzz-version }}
ACTION_ROOT: ${{ github.action_path }}/../../..
run: |
if [ "$(uname -s)" != "Linux" ] || [ "$(uname -m)" != "x86_64" ]; then
echo "::error::the released chuzz-headless host supports Linux x86_64"
exit 1
fi
version="$REQUESTED_VERSION"
if [ -z "$version" ]; then
version=$(sed -n 's/^version = "\([^"]*\)"/\1/p' "$ACTION_ROOT/Cargo.toml" | head -1)
fi
case "$version" in
''|*[!0-9A-Za-z.+-]*)
echo "::error::invalid chuzz version '$version'"
exit 1
;;
esac

asset=chuzz-headless-x86_64-unknown-linux-gnu.tar.gz
release=https://github.com/pathscale/chuzz/releases/download/headless-v$version
directory="$RUNNER_TEMP/chuzz-headless-$version"
mkdir -p "$directory"

if curl -fsSL --retry 3 --retry-delay 2 "$release/$asset" -o "$directory/$asset" && \
curl -fsSL --retry 3 --retry-delay 2 "$release/$asset.sha256" -o "$directory/$asset.sha256" && \
(cd "$directory" && sha256sum -c "$asset.sha256"); then
tar -xzf "$directory/$asset" -C "$directory"
chmod +x "$directory/chuzz-headless"
if [ "$(cat "$directory/VERSION")" != "$version" ]; then
echo "::error::released browser version does not match $version"
exit 1
fi
echo "released=true" >> "$GITHUB_OUTPUT"
echo "using released chuzz-headless $version"
else
echo "::warning::no verified chuzz-headless $version release; building ${{ inputs.chuzz-ref }} from source"
echo "released=false" >> "$GITHUB_OUTPUT"
fi
echo "host=$directory/chuzz-headless" >> "$GITHUB_OUTPUT"

- name: Check out chuzz source fallback
if: steps.acquire.outputs.released != 'true'
uses: actions/checkout@v4
with:
repository: pathscale/chuzz
Expand All @@ -56,26 +102,30 @@ runs:
shell: bash
run: |
sudo apt-get update
sudo apt-get install -y pkg-config libfontconfig1-dev fonts-dejavu-core
packages=(fontconfig fonts-dejavu-core)
if [ "${{ steps.acquire.outputs.released }}" != "true" ]; then
packages+=(pkg-config libfontconfig1-dev)
fi
sudo apt-get install -y "${packages[@]}"

- uses: Swatinem/rust-cache@v2
if: steps.acquire.outputs.released != 'true'
with:
workspaces: .qa-host
# A failing check must not make the corrective run rebuild a browser
# engine from zero.
cache-on-failure: true

# The driver links no renderer, so it installs from the registry. The host
# is built here because it is the browser, and the browser is not a crate a
# site can install.
# The driver links no renderer, so it installs from the registry. The
# browser is a versioned release asset built once by Chuzz's release job.
- name: Install ps-qa
shell: bash
env:
CARGO_TARGET_DIR: .qa-host/target/qa-tools
run: cargo install ps-qa --version "${{ inputs.ps-qa-version }}"

- name: Build the host
id: build
- name: Build source fallback
if: steps.acquire.outputs.released != 'true'
shell: bash
run: |
# `--no-default-features` drops `gui`, and with it `tauri`, `muda -> gtk`
Expand All @@ -84,4 +134,4 @@ runs:
cargo build --release --manifest-path .qa-host/Cargo.toml \
--bin chuzz-headless --no-default-features \
--features capture,javascript,scrollbars,webp,system-fonts
echo "host=$PWD/.qa-host/target/release/chuzz-headless" >> "$GITHUB_OUTPUT"
cp .qa-host/target/release/chuzz-headless "${{ steps.acquire.outputs.host }}"
77 changes: 71 additions & 6 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,52 @@ jobs:
- name: Test
run: cargo test -p chuzz-control --all-features

# The fleet's website QA consumes this exact feature set as a prebuilt Linux
# release asset. Build and drive it here once, in the repository that owns
# the browser, before a version can reach the release workflow.
headless:
runs-on: ubicloud-standard-4
steps:
- uses: actions/checkout@v4

- name: Install Rust stable
uses: dtolnay/rust-toolchain@stable

- name: Install native font dependencies
run: |
sudo apt-get update
sudo apt-get install -y pkg-config libfontconfig1-dev fonts-dejavu-core

- name: Cache cargo build
uses: Swatinem/rust-cache@v2
with:
shared-key: chuzz-linux-headless-ci
cache-targets: true

- name: Build and package the headless browser
run: scripts/package-headless.sh

- name: Test the packaged feature set
run: |
cargo test --release -p chuzz \
--no-default-features \
--features capture,javascript,scrollbars,webp,system-fonts \
--test serves_inspection

- name: Verify the release archive
run: |
cd target/headless-release
sha256sum -c chuzz-headless-x86_64-unknown-linux-gnu.tar.gz.sha256
tar -tzf chuzz-headless-x86_64-unknown-linux-gnu.tar.gz

- name: Keep the candidate for review
uses: actions/upload-artifact@v4
with:
name: chuzz-headless-x86_64-unknown-linux-gnu
path: target/headless-release/chuzz-headless-x86_64-unknown-linux-gnu.tar.gz*
if-no-files-found: error
retention-days: 7

# The frontend, which is where most changes actually land. Independent of the
# Rust jobs, so a TypeScript error does not wait on a cargo build.
#
Expand Down Expand Up @@ -121,12 +167,9 @@ jobs:
- name: Test
run: bun run test:run

# The app itself, on the platform it targets. This is the job that proves a
# change compiles against the pinned engine, which no Linux runner can do.
#
# `build-app.sh` is deliberately not used: bundling, signing and stamping are
# the release job's business. A PR only needs to know the code compiles and
# its tests pass.
# The app itself, on the platform it targets. The visible gate drives the
# same signed bundle a release would publish, including browser chrome,
# settings, theme controls, identity switching and the redraw stress page.
app:
runs-on: namespace-profile-agency-tahoe
steps:
Expand Down Expand Up @@ -159,3 +202,25 @@ jobs:

- name: Test
run: cargo test --workspace

- name: Install the native QA harness
run: cargo install ps-qa --version '^0.7'

- name: Build the review bundle
run: |
cargo build --release -p chuzz-qa-fixture
apps/chuzz/build-app.sh release

- name: Drive the visible application
env:
QA_ARTIFACT_DIR: ${{ runner.temp }}/chuzz-visible-qa
run: scripts/qa-visible-app.sh

- name: Keep visible QA evidence
if: always()
uses: actions/upload-artifact@v4
with:
name: chuzz-visible-qa
path: ${{ runner.temp }}/chuzz-visible-qa
if-no-files-found: warn
retention-days: 7
87 changes: 87 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ jobs:
outputs:
version: ${{ steps.decide.outputs.version }}
publish: ${{ steps.decide.outputs.publish }}
headless_publish: ${{ steps.decide.outputs.headless_publish }}
steps:
- name: Checkout
uses: actions/checkout@v4
Expand Down Expand Up @@ -101,6 +102,73 @@ jobs:
echo "Live is already $LIVE; nothing to do. Bump [workspace.package] version to release."
fi

HEADLESS=https://github.com/pathscale/chuzz/releases/download/headless-v$VERSION/chuzz-headless-x86_64-unknown-linux-gnu.tar.gz
if [ "${{ inputs.force }}" = "true" ]; then
echo "headless_publish=true" >> "$GITHUB_OUTPUT"
elif curl -fsSIL --max-time 30 "$HEADLESS" >/dev/null 2>&1 && \
curl -fsSIL --max-time 30 "$HEADLESS.sha256" >/dev/null 2>&1; then
echo "headless_publish=false" >> "$GITHUB_OUTPUT"
echo "The Linux headless asset for $VERSION is already published."
else
echo "headless_publish=true" >> "$GITHUB_OUTPUT"
echo "The Linux headless asset for $VERSION is missing; publishing it."
fi

# Build the Linux QA browser once per Chuzz release. Fleet repositories use
# the checksum-verified asset through the composite headless-host action;
# they no longer each compile the same renderer and browser graph.
headless:
needs: check
if: needs.check.outputs.headless_publish == 'true'
runs-on: ubicloud-standard-4
permissions:
contents: write
steps:
- name: Checkout
uses: actions/checkout@v4

- name: Install Rust stable
uses: dtolnay/rust-toolchain@stable

- name: Install native font dependencies
run: |
sudo apt-get update
sudo apt-get install -y pkg-config libfontconfig1-dev fonts-dejavu-core

- name: Cache cargo build
uses: Swatinem/rust-cache@v2
with:
shared-key: chuzz-linux-headless-release
cache-targets: true

- name: Build and package the headless browser
run: scripts/package-headless.sh

- name: Test the packaged feature set
run: |
cargo test --release -p chuzz \
--no-default-features \
--features capture,javascript,scrollbars,webp,system-fonts \
--test serves_inspection

- name: Publish the versioned headless asset
env:
GH_TOKEN: ${{ github.token }}
VERSION: ${{ needs.check.outputs.version }}
run: |
tag="headless-v$VERSION"
if ! gh release view "$tag" >/dev/null 2>&1; then
gh release create "$tag" \
--target "$GITHUB_SHA" \
--title "Chuzz headless $VERSION" \
--notes "Linux x86_64 native ps-qa host with capture, JavaScript, scrollbars, WebP, and system-font discovery." \
--prerelease
fi
gh release upload "$tag" \
target/headless-release/chuzz-headless-x86_64-unknown-linux-gnu.tar.gz \
target/headless-release/chuzz-headless-x86_64-unknown-linux-gnu.tar.gz.sha256 \
--clobber

release:
needs: check
if: needs.check.outputs.publish == 'true'
Expand Down Expand Up @@ -171,6 +239,9 @@ jobs:
with:
targets: aarch64-apple-darwin

- name: Install the native QA harness
run: cargo install ps-qa --version '^0.7'

- name: Cache cargo build
uses: Swatinem/rust-cache@v2
with:
Expand Down Expand Up @@ -207,6 +278,22 @@ jobs:
exit 1
fi

- name: Drive the exact release bundle
env:
QA_ARTIFACT_DIR: ${{ runner.temp }}/chuzz-visible-qa
run: |
cargo build --release -p chuzz-qa-fixture
scripts/qa-visible-app.sh "$BUNDLE_DIR/Chuzz.app"

- name: Keep visible QA evidence
if: always()
uses: actions/upload-artifact@v4
with:
name: chuzz-visible-qa-release-${{ needs.check.outputs.version }}
path: ${{ runner.temp }}/chuzz-visible-qa
if-no-files-found: warn
retention-days: 14

# Tauri produces this tarball as a side effect of its bundler. Chuzz has no
# Tauri, so it is packed here. COPYFILE_DISABLE stops bsdtar writing an
# AppleDouble `._` sidecar beside every file carrying an extended
Expand Down
7 changes: 4 additions & 3 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,6 @@
node_modules/
apps/chuzz/dist/

# Handover notes are session state, tracked locally and never committed.
docs/HANDOVER-*.md

# Local-only renderer patch, see the file itself.
.cargo/config.toml
.cargo/config.toml.disabled
Expand All @@ -22,6 +19,10 @@ apps/chuzz/gen/
# exist on one machine. See scripts/local-engine.sh.
.cargo/local-engine.toml

# Handover and session notes are working state, not repository content.
HANDOVER*.md
docs/HANDOVER*.md

# Lockfiles are not committed here. Every dependency is a caret range on a
# published version, so a build resolves the newest thing that satisfies it and
# a broken upstream release fails the build that introduced it. A committed lock
Expand Down
21 changes: 21 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,27 @@
- Work on a branch and ship through a pull request. Do not commit to `main`.
- Run `cargo fmt --all -- --check`, `cargo clippy --workspace --all-targets --all-features -- -D warnings`, and `cargo test --workspace --all-features` before delivery.

## Invariants (do not break these)

- **No Python.** Not a script, not `python3 -c`, not a heredoc. Do not swap it
for another ad hoc parser or assume `jq` is present: it does not ship with
macOS. Ask the tool that owns the answer for structured output. A fixed-shape
field can use one `sed -nE` line; logic that needs real parsing belongs in
this repository's Rust code, where it can be tested.

## Handover documents are never committed

Never commit a handover, status or session-summary document to this repository.
They are working notes for the owner and belong in the conversation. If one is
tracked, untrack it rather than editing it.

## Merged is not fixed

Only the owner closes a bug. A merged PR means the change shipped. Call it
finished only after the owner confirms it or after the exact reported path has
been driven on a running build. When that cannot be verified here, state what
remains unverified.

## Git workflow

- **Always specify the branch when pushing**: `git push origin branch-name`
Expand Down
Loading
Loading