build: ship the explorer's web UI in the agentenv-framework wheel - #52
earakely-scale wants to merge 3 commits into
Conversation
The wheel carried only the explorer's API: the server serves a built UI from agent_env/explorer/static/, but no release step built it, so `agent-env up` from PyPI served JSON at / and printed a /docs link that 404s. The publish workflow now builds the UI's static export in its own job, which can't mint a PyPI token and uses no cache, so no npm package runs where it could publish. The framework job copies the export into explorer/static/ before building and fails if the wheel lacks index.html, and the protocol job waits for the UI too, so a failed UI build publishes nothing. explorer/static/ is git-ignored and listed as a hatch artifact. The sdist excludes the UI's node_modules, .next and out, which hatch otherwise packs because it reads only the root .gitignore: building in place made a 159 MB sdist, over PyPI's limit. The ui job on pull requests packages the export the same way and checks both, so a regression shows up before a release. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
| python -m pip install --quiet --index-url https://pypi.org/simple uv | ||
| cp -R src/agent_env/explorer/ui/out src/agent_env/explorer/static | ||
| uv build --no-sources --out-dir dist | ||
| unzip -l dist/*.whl | grep -q 'agent_env/explorer/static/index.html' || { echo "::error::the wheel has no agent_env/explorer/static/index.html"; exit 1; } |
There was a problem hiding this comment.
Missing page assets pass checks
This check and the release check only look for index.html. A wheel can pass both while missing the _next files the explorer serves, leaving users with a page whose scripts or styles do not load. Check for a built asset in the wheel too.
Prompt To Fix With AI
This is a comment left during a code review.
Path: .github/workflows/local-backends.yml
Line: 86
Comment:
**Missing page assets pass checks**
This check and the release check only look for `index.html`. A wheel can pass both while missing the `_next` files the explorer serves, leaving users with a page whose scripts or styles do not load. Check for a built asset in the wheel too.
---
For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.There was a problem hiding this comment.
Same point as the later comment on line 87. It's fixed in b968051: both checks run .github/scripts/check_explorer_ui.py, which fails unless every /_next/ asset that index.html references is in the wheel. Details are in the reply there.
Hatch reads only the root .gitignore, so packing src/agent_env/explorer/ui/ took whatever a working tree held there: node_modules, build output, or an .env.local with credentials. The sdist now ships the UI the way the wheel does, as its built export, and its source stays in the repository. The sdist's copy of the UI source was already incomplete: the root .gitignore's lib/ dropped all of ui/src/lib/, so it could not rebuild the UI anyway. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
| cp -R src/agent_env/explorer/ui/out src/agent_env/explorer/static | ||
| uv build --no-sources --out-dir dist | ||
| unzip -l dist/*.whl | grep -q 'agent_env/explorer/static/index.html' || { echo "::error::the wheel has no agent_env/explorer/static/index.html"; exit 1; } | ||
| if tar tzf dist/*.tar.gz | grep -q '/explorer/ui/'; then echo "::error::the sdist includes the UI's source tree"; exit 1; fi |
There was a problem hiding this comment.
Source files escape archive check
If the sdist contains explorer/ui/ and tar still has paths to list, grep -q stops at the first match and closes the pipe. With pipefail, tar can then fail from a broken pipe, making the if condition false. The job passes instead of flagging the files. Let grep read the full listing.
| if tar tzf dist/*.tar.gz | grep -q '/explorer/ui/'; then echo "::error::the sdist includes the UI's source tree"; exit 1; fi | |
| if tar tzf dist/*.tar.gz | grep '/explorer/ui/' >/dev/null; then echo "::error::the sdist includes the UI's source tree"; exit 1; fi |
Prompt To Fix With AI
This is a comment left during a code review.
Path: .github/workflows/local-backends.yml
Line: 87
Comment:
**Source files escape archive check**
If the sdist contains `explorer/ui/` and `tar` still has paths to list, `grep -q` stops at the first match and closes the pipe. With `pipefail`, `tar` can then fail from a broken pipe, making the `if` condition false. The job passes instead of flagging the files. Let `grep` read the full listing.
```suggestion
if tar tzf dist/*.tar.gz | grep '/explorer/ui/' >/dev/null; then echo "::error::the sdist includes the UI's source tree"; exit 1; fi
```
---
For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.There was a problem hiding this comment.
Good point. Both checks now run .github/scripts/check_explorer_ui.py. It reads index.html from the wheel and fails unless every /_next/ script, stylesheet and font that page references is in the wheel too. Writing it surfaced a subtlety: the catch-all page's chunk is referenced percent-encoded (%5B%5B...slug%5D%5D) but stored as [[...slug]], so references are decoded before the lookup. Against real wheels: the full UI wheel passes, the same wheel with _next/ stripped fails and names all 14 missing assets, and the current UI-less 0.9.1265 wheel fails on the missing index.html. tst/unit/test_check_explorer_ui.py covers those cases plus the encoded reference.
Both UI checks looked only for index.html, so a wheel missing the _next/ scripts, stylesheets and fonts would pass and serve a page that never loads. .github/scripts/check_explorer_ui.py reads index.html from the wheel and fails unless every /_next/ asset it references is there; references are percent-decoded, since the catch-all page's chunk is referenced as %5B%5B...slug%5D%5D but stored as [[...slug]]. The release and the ui job both run it, and its unit tests cover a complete wheel, missing assets, a missing index.html, an index with no built assets and the encoded reference. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
What and why
The published wheel carries only the explorer's API. The server serves a built UI from
agent_env/explorer/static/(packaged_ui_dir()), but no release step builds one. Theuijob builds the static export on every PR as a check and throws it away. So withpip install 'agentenv-framework[explorer]',agent-env upserves JSON at/and prints a…/docs (UI)link that returns 404. This PR ships the UI in every release. It stays in the framework under the[explorer]extra rather than becoming a separate package, as agreed with @polakamtejas.explorer-uijob inpublish-pypi.ymlrunsnpm ci && npm run build:staticand uploads the export as a workflow artifact.contents: readonly, and uses no cache, since caches are a known release-poisoning vector.frameworkdownloads the export intoexplorer/static/beforeuv build. A new step runs.github/scripts/check_explorer_ui.py, which fails the publish unless the wheel hasagent_env/explorer/static/index.htmland every/_next/script, stylesheet and font that page references. Unit tests are intst/unit/test_check_explorer_ui.py.protocolnow waits forexplorer-uitoo, so a failed UI build publishes nothing, instead of shipping the protocol and stopping before the framework.explorer/static/is git-ignored so the build output can't be committed, and it's listed as a hatchartifactso it still ships.src/agent_env/explorer/ui/entirely, as the wheel does, and ships the UI as its built export. Hatch reads only the root.gitignore, so packingui/took whatever a working tree held there. Building the UI in place produced a 159 MB sdist, over PyPI's 100 MB limit, and a local.env.localwould have shipped too (Greptile's catch). The sdist's copy of the UI source was already incomplete anyway: the rootlib/pattern dropped all 42 files ofui/src/lib/.uijob now packages the export the way the release does. It runs the same script, and fails if the sdist contains anything underexplorer/ui/, so a packaging regression shows up on a PR rather than mid-release.Licensing was already covered:
THIRD_PARTY_NOTICES.mdhas an "Explorer UI dependencies" section that anticipates a distribution including the built UI, and that file ships in the wheel.How it was tested
node_modulespresent:index.html, no UI source, and is 2.5 MB (1.1 MB today).explorer/ui/, and carries the built UI. With a plantedui/.env.localholding a sentinel, the sdist has 0.env.localentries and no file containing the sentinel.git statusstays clean.uv build --no-sources:twine check --strictpasses._next/stripped (all 14 missing assets named), and the current UI-less 0.9.1265 wheel (missingindex.html).[explorer]and ranagent-env up:/,/docsand/environmentsserve the UI, and/api/v1/envsstill serves the API.check-jsonschema(vendor.github-workflows) andactionlintwithshellcheckpass on both workflows.publish-pypi.yml(explorer-ui→protocol→framework→release) also passesactionlint.bump-versionis set, so the release this merge triggers is the first to ship the UI. I'll install it from PyPI afterwards and confirmagent-env upserves the explorer.🤖 Generated with Claude Code
The PR appears safe to merge, though two earlier, non-blocking packaging checks remain incomplete.
Fix with agent prompt
Summary
Release builds now put the explorer’s static web UI in the framework wheel and source distribution, while leaving the UI source in the repository. PR workflows also build and check these distributions so missing UI files or bundled UI source are caught before release.
Reviews (3) · Last reviewed commit: "build: check every asset the explorer's ..."