Skip to content
Merged
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
13 changes: 7 additions & 6 deletions .agents/skills/ci-and-devcontainer/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,12 +33,13 @@ description: Use when changing Rust toolchain setup, Linux GUI dependencies, CI,
starts the container and runs its lifecycle commands, so a broken environment fails
before merge.
- Landing (`landing.yml`): on every push to main, or by hand, runs `just landing-build`
and deploys `apps/landing/dist` to GitHub Pages under `/study/`. A fresh checkout has
no captures of the app, so the recipe first makes all of them (`just docs-media`,
`just demo`) on the showcase's private desktop with software Vulkan, as the setup
action's extra packages provide, then builds the site with Bun, pinned by
`packageManager` in `apps/landing/package.json` to the Dockerfile's `BUN_VERSION`. The
README's demo GIF is published with the site; nothing it makes is committed.
and deploys `apps/landing/dist` to GitHub Pages under `/study/`. It builds no Rust: the
captures of the app are committed, and the site builds with Bun alone, pinned by
`packageManager` in `apps/landing/package.json` to the Dockerfile's `BUN_VERSION`.
- Captures (`captures.yml`): by hand only. Runs `just captures` on the showcase's private
desktop with software Vulkan, as the setup action's extra packages provide, and opens
(or updates) a pull request from the `captures` branch with what changed, then starts
CI on it, since a pull request a workflow opens starts none itself.
- Releases: Linux x64 and ARM64 build inside the devcontainer; macOS Intel/Apple Silicon
and Windows x64 use native GitHub runners. They install Rust and pinned cargo-packager;
they must never require a second devcontainer.
Expand Down
4 changes: 2 additions & 2 deletions .agents/team/roles/site.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,8 @@ Then update what they affect:
name in both languages (`.mdx` when it shows captures, `.md` otherwise);
- the captures of the app: scenes in `crates/study-showcase/src/docs.rs`, made by
`just docs-media` into `apps/landing/src/assets/shots/` and `clips/` as
`{scene}-{en|it}-{light|dark}`. They are never committed; the Landing workflow makes them
again on every deploy, so change the scene, never the file.
`{scene}-{en|it}-{light|dark}`. They are committed, but only as the scene makes them: change
the scene and make them again, never edit the file.

## Rules

Expand Down
61 changes: 61 additions & 0 deletions .github/workflows/captures.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
name: Captures

# Makes every capture of the app the website shows again, from the code on the branch it runs
# on: the docs' stills and clips and the README's demo GIF (`just captures`), on the
# showcase's private desktop with software Vulkan. It takes a while, so it runs only by hand,
# when the app's look has changed.
#
# What changed goes to a pull request from the `captures` branch, opened or updated here, to
# look over and merge; nothing is pushed to main. A pull request a workflow opens starts no
# workflow itself, so this starts CI on it.
on:
workflow_dispatch:

permissions:
contents: write
pull-requests: write
actions: write

concurrency:
group: captures
cancel-in-progress: true

jobs:
captures:
name: Make the captures
runs-on: ubuntu-24.04
steps:
- name: Check out
uses: actions/checkout@v7
- name: Set up
uses: ./.github/actions/setup
with:
# The private headless desktop the showcase records on, with software Vulkan.
packages: >-
sway grim wtype wf-recorder ffmpeg
libvulkan1 mesa-vulkan-drivers libgl1-mesa-dri libegl-mesa0
# The app runs for real here, so it is built as in development.
optimize-dependencies: 'true'
- name: Capture the app
run: just captures
- name: Open a pull request
env:
GH_TOKEN: ${{ github.token }}
BASE: ${{ github.ref_name }}
run: |
git add --all apps/landing/src/assets/shots apps/landing/src/assets/clips apps/landing/public/demo.gif
if git diff --cached --quiet; then
echo "The captures are current."
exit 0
fi
git config user.name 'github-actions[bot]'
git config user.email '41898282+github-actions[bot]@users.noreply.github.com'
git switch -c captures
git commit -m "Make the website's captures again" -m "Made by the Captures workflow from $BASE at $GITHUB_SHA."
git push --force origin captures
if [ "$(gh pr view captures --json state -q .state 2>/dev/null)" != OPEN ]; then
gh pr create --base "$BASE" --head captures \
Comment thread
nicksan222 marked this conversation as resolved.
--title "Make the website's captures again" \
--body "The Captures workflow made every still, clip and the demo GIF again from \`$BASE\` at $GITHUB_SHA. Look them over before merging."
fi
gh workflow run ci.yml --ref captures
21 changes: 7 additions & 14 deletions .github/workflows/landing.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,8 @@ name: Landing
# Builds the website in apps/landing/ (Astro, static) and publishes it to GitHub Pages at
# https://nicksan222.github.io/study/, so astro.config sets `base: '/study'`.
#
# Every capture of the app on the site is made here, from the code being deployed: the docs'
# stills and clips (`just docs-media`) and the README's demo GIF (`just demo`), which the
# site publishes at /study/demo.gif. None of them is committed, so any change to the app
# runs this to keep them current.
# It builds no Rust: the captures of the app the site shows are committed, so a deploy takes
# about a minute. The Captures workflow makes them again, by hand, in a pull request.
on:
push:
branches: [main]
Expand All @@ -27,21 +25,16 @@ jobs:
steps:
- name: Check out
uses: actions/checkout@v7
- name: Set up
uses: ./.github/actions/setup
# The same pin as the setup action's, which this job does not otherwise need.
- name: Install just
uses: taiki-e/install-action@v2
with:
# The private headless desktop the showcase records on, with software Vulkan.
packages: >-
sway grim wtype wf-recorder ffmpeg
libvulkan1 mesa-vulkan-drivers libgl1-mesa-dri libegl-mesa0
# The app runs for real here, so it is built as in development.
optimize-dependencies: 'true'
tool: just@1.40.0
- name: Set up Bun
uses: oven-sh/setup-bun@v2
with:
bun-version-file: apps/landing/package.json
# A fresh checkout has no captures, so this makes every one, then builds.
- name: Capture the app and build
- name: Build
run: just landing-build
- name: Upload
uses: actions/upload-pages-artifact@v5
Expand Down
5 changes: 0 additions & 5 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,3 @@ core.*
/apps/landing/dist/
/apps/landing/.astro/

# Captures of the app, made from sample data on every website build (`just docs-media`,
# `just demo`), never committed.
/apps/landing/src/assets/shots/
/apps/landing/src/assets/clips/
/apps/landing/public/demo.gif
5 changes: 3 additions & 2 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -89,8 +89,9 @@ it and the app from zero, and `just desktop-watch` restarts the app on every cha
- `just demo`: records the README's demo GIF from sample data on its own private desktop
(no sign-in, no model, not the shared desktop), into `apps/landing/public/demo.gif`; the
site publishes it and the README shows it from there. `just docs-media` makes the docs'
stills and clips the same way. None of them is committed: the Landing workflow makes all
of them on every deploy, and `just landing` makes any that are missing. Edit the tour in
stills and clips the same way, and `just captures` makes both. They are committed: the
website deploys them as they are, and the Captures workflow, run by hand, makes them all
again and opens a pull request with them. Edit the tour in
`crates/study-showcase/src/tour.rs`, the docs' scenes in `src/docs.rs`.
- `just agents`: maintainer tooling in the default devcontainer.
Herdr runs one project-wide team: lead, developer, PM, QA, student, reviewer and pushback,
Expand Down
11 changes: 6 additions & 5 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,11 +83,12 @@ Both follow `XDG_DATA_HOME` and `XDG_CACHE_HOME`. `just run` uses `target/dev-da

## Captures of the app

The README's demo GIF and every still and clip on the website are made from the code, never
committed. The Landing workflow makes them all on every push to main, from sample data on a
private virtual desktop, and publishes them with the site; the README shows the GIF from
there. Locally, `just landing` and `just landing-build` make any that are missing, and
`just docs-media` and `just demo` make them again after a change. Edit the GIF's tour in
The README's demo GIF and every still and clip on the website are made from the code, on
sample data on a private virtual desktop, and committed; the Landing workflow publishes them
with the site as they are, and the README shows the GIF from there. When the app's look
changes, run the Captures workflow by hand: it makes them all again and opens a pull request
with them to look over and merge. Locally, `just captures` does the same, `just docs-media`
and `just demo` make one kind, and `just docs-media missing` makes only a new scene's. Edit the GIF's tour in
`crates/study-showcase/src/tour.rs` and the docs' scenes in `crates/study-showcase/src/docs.rs`.

## The agent team
Expand Down
17 changes: 9 additions & 8 deletions Justfile
Original file line number Diff line number Diff line change
Expand Up @@ -168,22 +168,23 @@ demo output="apps/landing/public/demo.gif":
cargo build --locked -p study
cargo run --locked -p study-showcase -- "$1"

# Make the website's docs stills and clips (apps/landing/src/assets/shots and clips) in every language and theme, the same way; name scenes or variants (`search it-dark`) to make only those, or `missing` for those not made yet.
# Make the website's docs stills and clips (apps/landing/src/assets/shots and clips) in every language and theme, the same way; name scenes or variants (`search it-dark`) to make only those, or `missing` for those not made yet, as for a new scene.
docs-media *only:
cargo build --locked -p study
cargo run --locked -p study-showcase -- docs {{only}}

# Make the website's captures of the app that are missing: none is committed, so a fresh checkout (and every Landing workflow run) makes them all, and a new scene only its own. `just docs-media` and `just demo` make them again after a change.
landing-media:
just docs-media missing
[ -s apps/landing/public/demo.gif ] || just demo
# Make every capture of the app the website shows again, as the Captures workflow does: the docs' stills and clips, then the demo GIF. The old files go first, so a scene dropped from docs.rs leaves none behind.
captures:
rm -f apps/landing/src/assets/shots/* apps/landing/src/assets/clips/*
just docs-media
just demo

# Serve the website (apps/landing: the landing page and docs) with live reload at localhost:4321/study/.
landing: landing-media
landing:
cd apps/landing && bun install --frozen-lockfile && bun run dev --host

# Build the website into apps/landing/dist, as the Landing workflow publishes it, with every capture of the app.
landing-build: landing-media
# Build the website into apps/landing/dist, as the Landing workflow publishes it, from the committed captures; it fails if one is missing.
landing-build:
cd apps/landing && bun install --frozen-lockfile && bun run build

# Type-check the website's pages, components and content without building it, so it needs no captures.
Expand Down
Binary file added apps/landing/public/demo.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file not shown.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file not shown.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file not shown.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file not shown.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file not shown.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file not shown.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file not shown.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file not shown.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file not shown.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file not shown.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file not shown.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file not shown.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file not shown.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file not shown.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file not shown.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file not shown.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file not shown.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file not shown.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file not shown.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file not shown.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file not shown.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file not shown.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file not shown.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file not shown.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added apps/landing/src/assets/shots/home-en-dark.webp
Binary file added apps/landing/src/assets/shots/home-en-light.webp
Binary file added apps/landing/src/assets/shots/home-it-dark.webp
Binary file added apps/landing/src/assets/shots/home-it-light.webp
5 changes: 3 additions & 2 deletions crates/study-showcase/src/desktop.rs
Original file line number Diff line number Diff line change
Expand Up @@ -435,10 +435,11 @@ impl Recording {

/// Asks the recorder to finish the file and waits for it. A still screen sends the
/// recorder no frame to notice the request on, so the pointer, resting at `at`, is
/// nudged until it ends and comes back there.
/// nudged until it ends and comes back there. On a small machine rendering in software
/// (a CI runner) it may still be encoding the frames it holds, so it gets a minute.
pub fn stop(mut self, desktop: &Desktop, at: (u32, u32)) -> Result<()> {
signal(&self.process.0, "INT").context("cannot stop wf-recorder")?;
let ended = wait(Duration::from_secs(10), "wf-recorder to finish", || {
let ended = wait(Duration::from_secs(60), "wf-recorder to finish", || {
let moved = desktop
.warp(at.0.saturating_sub(2), at.1)
.and_then(|()| desktop.warp(at.0, at.1));
Expand Down
34 changes: 32 additions & 2 deletions crates/study-showcase/src/docs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -448,8 +448,8 @@ fn reset() -> Vec<Step> {
vec![key("Escape"), key("Escape"), rail(HOME), Step::Pause(800)]
}

/// The argument that makes only the scenes with a file missing, in every variant: what a
/// fresh checkout runs before building the site, so it never starts a desktop for nothing.
/// The argument that makes only the scenes with a file missing, in every variant: a new
/// scene's, without making again the ones already committed.
pub const MISSING: &str = "missing";

/// Makes every scene whose name is in `only` (all if it is empty, or those with a file
Expand Down Expand Up @@ -659,6 +659,36 @@ mod tests {
}
}

#[test]
fn the_committed_captures_are_every_scenes_files_and_no_others() {
let repo = Path::new(env!("CARGO_MANIFEST_DIR")).join("../..");
let expected: HashSet<PathBuf> = scenes()
.iter()
.flat_map(|scene| {
Variant::all()
.into_iter()
.flat_map(|variant| scene.files(&repo, variant))
})
.collect();
for file in &expected {
assert!(
file.is_file(),
"{} is missing: make it with `just docs-media missing`",
file.display()
);
}
for dir in [SHOTS, CLIPS] {
for entry in std::fs::read_dir(repo.join(dir)).unwrap() {
let file = entry.unwrap().path();
assert!(
expected.contains(&file),
"{} is no scene's capture: delete it",
file.display()
);
}
}
}

#[test]
fn the_theme_codes_are_the_desktops() {
let source = include_str!("../../../apps/desktop/src/app/preferences.rs");
Expand Down
5 changes: 3 additions & 2 deletions crates/study-showcase/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
//! Records the demo GIF in the README (`just demo`) and the website's docs media
//! (`just docs-media`). Both are build output, not sources: the Landing workflow makes them
//! again on every deploy and publishes the GIF with the site, where the README shows it.
//! (`just docs-media`). Both are output, not sources: they are committed so the website deploys
//! without building the app, and the Captures workflow makes them all again, by hand, in a
//! pull request. The site publishes the GIF, and the README shows it from there.
//!
//! The binary starts a private headless desktop (its own sway under `target/showcase`, never
//! the shared one from `just desktop`), seeds a fresh database with the sample data, opens
Expand Down