fix(ci): repair pages workflow so the site actually deploys#53
fix(ci): repair pages workflow so the site actually deploys#53
Conversation
Three bugs prevented stelekit.stapler.dev from ever being deployed: 1. `build-demo` always skipped — github.event.head_commit.modified is empty for squash-merged PR commits, so the kmp/ path filter never fired. Replaced with a `check-changes` job that uses git diff HEAD~1 HEAD, which works reliably for all push types. 2. `deploy` always skipped — GitHub Actions propagates the skipped state of `build-demo` transitively, causing `deploy` (which only depends on `build-site`) to also be skipped even though build-site ran and succeeded. Fixed by adding `if: always() && needs.build-site.result == 'success'`. 3. `DEMO_AVAILABLE` ignored the fallback download — if the artifact came from a previous run via the fallback step, DEMO_AVAILABLE was still false. Now checks both download steps. Also fixed the fallback script to exit 1 when no previous run exists so the outcome correctly reflects availability. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Android Load BenchmarkInstrumented benchmark on an API 30 x86_64 emulator measuring load performance for the Android app. Comparing
|
JVM Load Benchmark (Desktop)Synthetic in-memory benchmark measuring load performance for the desktop (JVM) app.
Flamegraphs (this PR)**Allocation** — object allocation pressure (JDBC/SQLite churn)Alloc flamegraph not available CPU — method-level hotspots by on-CPU time CPU flamegraph not available Top SQL queries by total time (this PR)| table:operation | calls | p50 | p99 | max | total | |-----------------|-------|-----|-----|-----|-------| | `pages:select` | 2 | 1ms | 1ms | 1ms | 1ms |Top allocation hotspots (this PR)`67.9%` byte[]_[k] `4.1%` java.lang.String_[k] `2.6%` jdk.internal.org.objectweb.asm.SymbolTable$Entry[]_[k] `2.6%` java.lang.StringBuilder_[k] `2.1%` int[]_[k]Top CPU hotspots (this PR)`99%` /usr/lib/x86_64-linux-gnu/libc.so.6 `0.1%` pread `0.1%` inflate `0.1%` app/cash/sqldelight/driver/jdbc/JdbcPreparedStatement.executeQuery_[0] `0.1%` SymbolTable::new_symbol |
There was a problem hiding this comment.
Pull request overview
Repairs the GitHub Pages deployment workflow so docs and the optional WASM demo artifact are built reliably and the deploy job runs even when upstream jobs are skipped.
Changes:
- Adds a
check-changesjob to detectkmp//Gradle-related changes and gatebuild-demo. - Fixes
deployjob gating so it runs whenbuild-sitesucceeds, even if upstream jobs were skipped. - Improves demo artifact handling (fallback download behavior and
DEMO_AVAILABLEcomputation).
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| fetch-depth: 2 | ||
| - name: Detect kmp/gradle changes | ||
| id: filter | ||
| # git diff HEAD~1 HEAD is reliable for squash merges and direct commits; | ||
| # github.event.head_commit.modified is often empty for PR-merged commits. | ||
| run: | | ||
| if git diff --name-only HEAD~1 HEAD | grep -qE '^(kmp/|gradle|settings\.gradle)'; then | ||
| echo "kmp=true" >> "$GITHUB_OUTPUT" | ||
| else | ||
| echo "kmp=false" >> "$GITHUB_OUTPUT" |
There was a problem hiding this comment.
git diff --name-only HEAD~1 HEAD only compares the last commit. On a push containing multiple commits, changes to kmp/ (or build files) earlier in the push can be missed and build-demo will be skipped incorrectly. Consider diffing the full push range instead (e.g., ${{ github.event.before }}..${{ github.sha }}) and adjusting fetch-depth accordingly.
| fetch-depth: 2 | |
| - name: Detect kmp/gradle changes | |
| id: filter | |
| # git diff HEAD~1 HEAD is reliable for squash merges and direct commits; | |
| # github.event.head_commit.modified is often empty for PR-merged commits. | |
| run: | | |
| if git diff --name-only HEAD~1 HEAD | grep -qE '^(kmp/|gradle|settings\.gradle)'; then | |
| echo "kmp=true" >> "$GITHUB_OUTPUT" | |
| else | |
| echo "kmp=false" >> "$GITHUB_OUTPUT" | |
| fetch-depth: 0 | |
| - name: Detect kmp/gradle changes | |
| id: filter | |
| # Compare the full pushed range so multi-commit pushes don't miss earlier changes. | |
| # github.event.head_commit.modified is often empty for PR-merged commits. | |
| run: | | |
| if [ "${{ github.event_name }}" != "push" ]; then | |
| echo "kmp=false" >> "$GITHUB_OUTPUT" | |
| else | |
| BEFORE="${{ github.event.before }}" | |
| AFTER="${{ github.sha }}" | |
| if [ "$BEFORE" = "0000000000000000000000000000000000000000" ]; then | |
| CHANGED_FILES="$(git ls-tree -r --name-only "$AFTER")" | |
| else | |
| CHANGED_FILES="$(git diff --name-only "$BEFORE" "$AFTER")" | |
| fi | |
| if printf '%s\n' "$CHANGED_FILES" | grep -qE '^(kmp/|gradle|settings\.gradle)'; then | |
| echo "kmp=true" >> "$GITHUB_OUTPUT" | |
| else | |
| echo "kmp=false" >> "$GITHUB_OUTPUT" | |
| fi |
| # git diff HEAD~1 HEAD is reliable for squash merges and direct commits; | ||
| # github.event.head_commit.modified is often empty for PR-merged commits. | ||
| run: | | ||
| if git diff --name-only HEAD~1 HEAD | grep -qE '^(kmp/|gradle|settings\.gradle)'; then |
There was a problem hiding this comment.
The change detector regex doesn't include root Gradle build scripts like build.gradle.kts (present in this repo) or buildSrc/, so edits to those files could still skip build-demo even though they affect the WASM build. Expand the match to include root build scripts (and any other build inputs you consider relevant).
| if git diff --name-only HEAD~1 HEAD | grep -qE '^(kmp/|gradle|settings\.gradle)'; then | |
| if git diff --name-only HEAD~1 HEAD | grep -qE '^(kmp/|buildSrc/|gradle/|gradlew(\.bat)?$|settings\.gradle(\.kts)?$|build\.gradle(\.kts)?$|gradle\.properties$)'; then |
| # True when demo files are present from either current run or fallback | ||
| DEMO_AVAILABLE: ${{ steps.download-demo.outcome == 'success' || steps.download-demo-fallback.outcome == 'success' }} |
There was a problem hiding this comment.
DEMO_AVAILABLE is being passed as an env var, but the site reads it via import.meta.env.DEMO_AVAILABLE (Astro/Vite env). By default, Vite only exposes env vars with the configured prefix (commonly PUBLIC_/VITE_), so this may remain undefined and evaluate to false even when set here. Align the variable name/prefix (or configure Vite envPrefix / switch the site to read process.env at build time) so the flag actually reaches the build.
| # True when demo files are present from either current run or fallback | |
| DEMO_AVAILABLE: ${{ steps.download-demo.outcome == 'success' || steps.download-demo-fallback.outcome == 'success' }} | |
| # True when demo files are present from either current run or fallback. | |
| # Use a public prefix so Astro/Vite exposes the value via import.meta.env. | |
| PUBLIC_DEMO_AVAILABLE: ${{ steps.download-demo.outcome == 'success' || steps.download-demo-fallback.outcome == 'success' }} |
There was a problem hiding this comment.
No change needed here. import.meta.env.DEMO_AVAILABLE is in Astro frontmatter (the --- block in demo.astro), which executes at build time on the server, not in the browser. In Astro, all env vars are accessible in frontmatter without any prefix; PUBLIC_ is only required for variables that need to be embedded into client-side JavaScript.
Validates that the compiled Kotlin/WASM binary actually runs in a browser, not just that the build directory is non-empty. Three assertions in sequence: 1. canvas#ComposeTarget is attached to the DOM (HTML loaded) 2. Canvas width grows past 300 px (Compose sized the canvas, meaning the WASM main() ran and CanvasBasedWindow initialized) 3. Centre WebGL pixel has non-zero alpha (Compose committed a paint frame, meaning the UI actually rendered) The test server (e2e/server.mjs) sets COOP/COEP headers directly, so coi-serviceworker.min.js sees crossOriginIsolated=true on the first navigation and skips its reload—no flaky redirect to handle. CI: pages.yml build-demo job now installs Playwright/Chromium after the Gradle build and runs the smoke test before uploading the artifact. The playwright-report artifact is preserved on failure for debugging. Changes to e2e/ also trigger build-demo (path filter updated). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…patterns Addresses two review comments on PR #53: 1. Use github.event.before instead of HEAD~1 so multi-commit pushes don't miss kmp/ changes that landed in earlier commits of the same push. Switches to fetch-depth: 0 so the before SHA is always reachable. Handles the all-zeros SHA (initial push / no before) by defaulting to kmp=true. 2. Expand the path regex to include build.gradle.kts (root build script) and buildSrc/ (both present in this repo), which affect the WASM build but were not previously triggering build-demo. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Summary
build-demowas always skipped becausegithub.event.head_commit.modifiedis empty for squash-merged PR commits — confirmed by checking thefeat(editor): Cmd+Kcommit, which touched 6kmp/files but still skipped the job. Replaced with acheck-changesjob that usesgit diff HEAD~1 HEAD(reliable for all push types).deploywas always skipped due to a known GitHub Actions quirk: when any job in the transitiveneedschain was skipped (build-demo), downstream jobs using the defaultsuccess()check are also skipped, even if the intermediate job (build-site) ran and succeeded. Fixed withif: always() && needs.build-site.result == 'success'.DEMO_AVAILABLEwas alwaysfalsewhen the artifact came from the fallback download step. Fixed to check both download outcomes. Also fixed the fallback script toexit 1when no previous run exists (was silently exiting 0, incorrectly indicating success).The site at stelekit.stapler.dev has
status: nullin the Pages API — it has never been deployed. These three bugs are the reason.Test plan
check-changesdetects the workflow file change (no kmp change) →kmp=falsebuild-demoskipped (no kmp change)build-siteruns, deploys docs-only site withDEMO_AVAILABLE=falsedeployruns and deploys to stelekit.stapler.devkmp/,build-demoruns and builds the WASMworkflow_dispatchor next kmp commit makes demo available🤖 Generated with Claude Code