diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7b87a764..50716f0a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -68,3 +68,95 @@ jobs: run: | nix shell nixpkgs#trealla --command tpl prolog-sessions/kb.pl \ -g "(kb_consistent -> halt(0) ; (kb_report, halt(1)))" + + # --------------------------------------------------------------------------- + # GitHub Pages delivery (improvise-d31). + # + # Two jobs on purpose. `pages-build` runs on every push and pull request, so + # a change that breaks `web/build.sh` or the bundle fails on the PR that made + # it — the build is a gate. `pages-deploy` runs only on a push to main and + # `needs` the two check jobs above, so nothing that failed `cargo test` or the + # wasm32 gate can reach the published site. The deploy depends on those checks + # rather than repeating them (improvise-d31 AC 2). + # + # Not path-filtered, deliberately. The bundle is `improvise-wasm-client` plus + # improvise-protocol plus improvise-core plus improvise-formula, so a filter + # that meant "skip when the bundle cannot have changed" would have to name + # most of the workspace, and the failure mode of getting it wrong is a stale + # site with no signal that it is stale. A redundant build is the cheaper error. + pages-build: + name: Browser bundle + static site + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable + with: + targets: wasm32-unknown-unknown + - uses: Swatinem/rust-cache@v2 + # Pinned release binary rather than `cargo install` (a three-minute + # source build) or a third-party action. wasm-pack runs its own bundled + # `wasm-opt`, which is why improvise-wasm-client's Cargo.toml names + # `--enable-bulk-memory`: without it the optimizer rejects the + # `memory.copy` LLVM emits regardless. + # v0.15.0 is what `nix develop` supplies, and the bundle was built and + # measured against it locally (346 KB, 120 KB gzipped). Pinning the same + # version is what makes that local run evidence about this one. + - name: Install wasm-pack + env: + WASM_PACK_VERSION: v0.15.0 + run: | + set -eu + url="https://github.com/rustwasm/wasm-pack/releases/download/${WASM_PACK_VERSION}/wasm-pack-${WASM_PACK_VERSION}-x86_64-unknown-linux-musl.tar.gz" + curl -sSfL "$url" | tar -xz --strip-components=1 -C /usr/local/bin \ + "wasm-pack-${WASM_PACK_VERSION}-x86_64-unknown-linux-musl/wasm-pack" + wasm-pack --version + + # The same script a developer runs locally, so there is one answer to + # "how is the bundle built" rather than two that can drift. + - name: Build the wasm bundle + run: ./web/build.sh + + # `web/` is the source directory: it also holds build.sh and .gitignore, + # which have no business being served. Assemble what ships. + - name: Assemble the site + run: | + set -eu + mkdir -p site + cp web/index.html web/app.js web/style.css site/ + cp -rf web/pkg site/pkg + rm -f site/pkg/.gitignore site/pkg/package.json + find site -type f | sort + du -sh site + + - uses: actions/upload-pages-artifact@v3 + with: + path: site + + pages-deploy: + name: Deploy to GitHub Pages + needs: [test, wasm, pages-build] + if: github.event_name == 'push' && github.ref == 'refs/heads/main' + runs-on: ubuntu-latest + permissions: + contents: read + pages: write + id-token: write + # A superseded deploy is allowed to finish rather than being cancelled: + # `deploy-pages` cancelled midway can leave the Pages deployment in a + # half-applied state, and the next push republishes anyway. + concurrency: + group: pages + cancel-in-progress: false + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + steps: + # `enablement: true` asks the API to turn Pages on with source = + # GitHub Actions. It succeeds only if the repository allows the workflow + # token to do so; if it does not, this step is where the run fails and + # the fix is the repository setting, not this file (improvise-3gy). + - uses: actions/configure-pages@v5 + with: + enablement: true + - id: deployment + uses: actions/deploy-pages@v4 diff --git a/README.md b/README.md index 69810dc5..ae669a87 100644 --- a/README.md +++ b/README.md @@ -176,6 +176,35 @@ improvise cmd 'add-cat Region' 'add-item Region East' -f model.improv improvise script setup.txt -f model.improv ``` +## In the browser + +The browser page is at ****, built +and published from `main` by the `pages-build` / `pages-deploy` jobs in +`.github/workflows/ci.yml`. A deploy only happens after `cargo test` and the +wasm32 check have passed. + +**It needs a server, and a static host is not one.** The page is a thin client: +it holds no model, and every question about a cell — its text, whether it may +be typed into — is answered by a server and arrives already decided. So the +published page paints an empty grid until it is pointed at one: + +```sh +cargo run -p improvise-ws-server -- examples/demo.improv --port 8787 +``` + +then open the page with `?ws=ws://127.0.0.1:8787`. Over https a `ws://` +address is blocked as mixed content, so for now a local server is most easily +paired with a local page: + +```sh +./web/build.sh # wasm-pack -> web/pkg (gitignored) +python3 -m http.server 8080 --directory web +``` + +Running with no separate process is the in-page worker, improvise-9x6, which +is not built yet. Until it lands, what is deployed is the delivery pipeline, +not a working demo. + ## What's interesting about the architecture All user actions flow through a two-phase command/effect pipeline. Commands diff --git a/flake.nix b/flake.nix index 8879847b..569cfd44 100644 --- a/flake.nix +++ b/flake.nix @@ -50,6 +50,12 @@ pkgs.universal-ctags beads.packages.${system}.default + # `web/build.sh` — and so the Pages job's local dry run — needs this. + # The `wasm` CI job only runs `cargo check`, which does not, so the + # shell was missing the one tool that turns a passing check into a + # loadable page (improvise-d31). + pkgs.wasm-pack + # Demo recording and release tooling pkgs.asciinema pkgs.vhs diff --git a/web/index.html b/web/index.html index bb28238f..b43c3eb5 100644 --- a/web/index.html +++ b/web/index.html @@ -34,6 +34,32 @@ + +

+ improvise in the browser is a thin client: the document lives in a + server. Run improvise-ws-server model.improv --port 8787 + and reload with ?ws=ws://127.0.0.1:8787. Served over + https, a ws:// address is blocked as mixed content — use + wss:// or a local http server. Running with no separate + process at all is the in-page worker (improvise-9x6), and it is not + built yet. +

+
diff --git a/web/style.css b/web/style.css index 43f00b56..0c3e9f7f 100644 --- a/web/style.css +++ b/web/style.css @@ -139,3 +139,17 @@ td input { color: inherit; background: transparent; } + +/* The page cannot reach a server from a static host, and says so. */ +#backend-notice { + margin: 0; + padding: 0.5rem 1rem; + border-bottom: 1px solid var(--line); + background: color-mix(in srgb, var(--accent) 10%, transparent); + font-size: 0.85rem; +} + +#backend-notice code { + background: color-mix(in srgb, var(--line) 45%, transparent); + padding: 0 0.25rem; +}