Verify tlon-apps UI changes (single-ship recipe)
SkillWeb & browsingVerify tlon-apps web UI changes end-to-end with a single local ship + vite dev server (lighter than the full playwright-dev rig)
Available today. Use it from your connected AI after setup.
No other account needed.
Connect ahel once, and every AI you use reads what you have installed.
Then ask your AI: use the Verify tlon-apps UI changes (single-ship recipe) skill
What this skill tells your AI
The instructions your AI receives, as published by tloncorp/tlon-apps in tools/agent-skills/verify/SKILL.md and read by ahel’s review.
The full ./start-playwright-dev.sh rig boots 2+ ships (2GB loom each) and
can get OOM-killed on low-memory machines mid desk-commit ("Ships process
exited unexpectedly with code null"). For UI verification a single ship +
one vite server is enough and much lighter.
Capabilities assumed (this is a recipe, not tied to one agent — any agent
with these can follow it): a shell (urbit binary, pnpm, curl, lsof),
filesystem access to the repo, and a browser-automation tool that can
navigate, type/fill inputs, click by selector, evaluate JS in the page, and
screenshot. Where noted, use your tool's own verbs for those actions.
Gotchas (hit these before, save the debugging)
- asdf: if the pnpm shim fails with "No version is set for command pnpm",
the pinned Node isn't installed. The repo pins 22.22.0 (
.nvmrcandapps/tlon-web/.tool-versions) — runasdf install nodejs 22.22.0, don't overrideASDF_NODEJS_VERSIONto a newer Node: 24+ has no prebuiltbetter-sqlite3@11.xbinaries and falls back to compiling via node-gyp. - peru: rube's desk assembly needs
peruon PATH (brew install pipx && pipx install peru). Without it rube dies at "Vendoring desk dependencies". - Process sandbox: launch the ship and web server as real, long-lived detached processes. Some agent execution sandboxes kill detached/background processes — if yours does, run these steps with the sandbox disabled.
Recipe
- Piers + urbit binary live in
apps/tlon-web/rube/dist/after any rube run (zod/, ten/, urbit_extracted/urbit). If missing, let./start-playwright-dev.shrun once to download/extract, then kill it. - Boot the zod ship (http port 35453 per
apps/tlon-web/e2e/shipManifest.json). First make sure zod's own port/pier isn't already live — deleting a live.vere.lockand booting a second Vere on the same pier corrupts/wedges it. Gate on zod specifically — its port:35453or a live process on thezod/zodpier. Check both, because a zod daemon that is still booting has created.vere.lockbut hasn't bound:35453yet; a port-only check would then delete a live lock and start a second Vere on the same pier (the exact corruption this guards against). The pier-path match keeps it zod-specific, so another rube ship like~ten(ten/ten,:38473) is correctly ignored and doesn't make you skip booting zod. The-dflag runs Vere as a detached daemon, so the boot returns and you can proceed:cd apps/tlon-web # Note the [r] trick: `pgrep -f 'rube/...'` would otherwise match the wrapper # shell running this very block (its command line contains the pattern) and # always report "live". `[r]ube` matches the ship process but not the literal # `[r]ube` in this shell's argv. if lsof -ti :35453 >/dev/null 2>&1 || pgrep -f '[r]ube/dist/zod/zod' >/dev/null; then echo "zod already live (port :35453 or zod/zod pier process) — reuse it; do NOT delete the lock or boot again." else rm -f rube/dist/zod/zod/.vere.lock ./rube/dist/urbit_extracted/urbit rube/dist/zod/zod -d --http-port 35453 # -d = daemon (detached) fi - Start the web server. It runs in the foreground and blocks, so launch it as
a long-lived background/detached process (your agent's background-run
mechanism, a separate terminal/session, or append
&) — otherwise the recipe hangs here and never reaches the browser step:cd apps/tlon-web # --strictPort: fail loudly if :3000 is taken instead of silently falling # through to the next free port (which would leave you verifying stale UI at # localhost:3000). If it fails, kill/reuse the existing :3000 server first. SHIP_URL=http://localhost:35453 VITE_DISABLE_SPLASH_MODAL=true pnpm dev-no-ssl --port 3000 --strictPort & - Browse
http://localhost:3000/apps/groups/, log in with zod codelidlut-tabwed-pillex-ridrup. Rube nukes ship state, so create a quick group via the + button to get a chat channel. - Vite serves
packages/appetc. from source (workspacemain: index.ts), so edits hot-reload; confirm served code withcurl -s "http://localhost:3000/apps/groups/@fs/<abs-path-to-file>" | grep <new-identifier>.
Browser-driving notes
- Synthesized
keyevents (cmd+a, Backspace, Return) do NOT reach the RN-web textarea. Use your browser tool's type/fill action instead. To "select-all + type over", set the value in one fill/input action (same as a real replace) rather than key-by-key. To send, click[data-testid="MessageInputSendButton"]. - The TanStack devtools floating toggle overlaps the send button bottom-right;
hide it first:
document.querySelector('button[aria-label="Open Tanstack query devtools"]').style.display='none'. - Screenshot coords are viewport × (screenshot_width / viewport_width) — get element rects via JS and scale.
- Link-preview metadata fetches go browser → ship metagrab → target URL, so a
local
nodeHTTP server with asetTimeoutresponse works as a slow/ controllable metadata target (ship can reach localhost).
Teardown
lsof -ti:3000 | xargs kill -9; lsof -ti:35453 | xargs kill -9; pkill -f "rube/dist/urbit_extracted"
Signals
- GitHub stars
- 108
- Forks
- 49
- Last commit
- Sep 2026
ahel review
K1binfo
installs-packages
Automated review, not a security audit. Ruleset v1+k2.
Advanced
- Catalog kind
- skill
- Gateway key
verify-tloncorp- Source
- github.com/tloncorp/tlon-apps