T6-2 (#38): Rust CODE panel inside the normal game UI #39
Loading…
Reference in a new issue
No description provided.
Delete branch "task/38-in-game-rust-panel"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
What
The Rust CODE panel now lives inside the normal game UI (issue #38, Phase 6): the CODE button opens a three-element
.codeuigroup — a tab strip, the legacy JS editor (#codeui, untouched), and the new Rust pane (#rustcodeui) — so the character is on screen while a Rust process drives it.One partial, one client, two hosts (the "reuse, don't fork" rule from the brief):
htmls/contents/rust_serve_panel.htmljs/rust_code_panel.jsPOST /compile|/run|/stop|/reload,GET /status|/options|/source, 1.5 s poll, one instance per mountjs/al_code_panes.jscode_pane("js"|"rust")+code_ui_sync(), called fromtoggle_code()htmls/rust_code.htmlhtmls/index.html,css/al_serve_panel.css,js/functions.jscode_ui_sync()callThe endpoint is chosen in the browser (
data-endpoint→?serve=→ localStorage →window.AL_SERVE_DEFAULT_ENDPOINT→127.0.0.1:8192); the backend renders no endpoint, no crate list and no Rust source, and the auth token never crosses the page or the backend. The RUST pane mounts lazily on first tab click — no request, no poll until someone asks for it — and stops polling when its tab or the CODE panel closes.Live verification (the part the previous two sessions did not reach)
Headless chromium over CDP against the worktree backend :8091 and
al serve:8212 (--creds rust/dev/.local, i.e. Rusty). The browser plays UserTesting and watches Rusty through the game's own observer (observe_character) — deliberately: the server refuses a second login for a character already in game (msince(last_sync) < 120 → ex("ingame"),node/server.js:10911), andal serveruns the crate as Rusty.al serveat page load,panel("game")absent → after the RUST tab click it is mounted, polling,endpoint() === http://127.0.0.1:8212(resolved through the?serve=step), crate list[examples/hello, examples/farm_basic, examples/capture], editor loaded 24 080 chars fromGET /source.al serve→Finished \release` profile [optimized] target(s) in 2.85s/[al] built …/target/release/hello`.Running · pid 2630111 · restarts 0 · examples/hello; ringhello: logged in to ws://127.0.0.1:7192/socket.io/ as "Rusty"; the log pane streamedx 2.4 y -21.2→x -11.0 y 98.1→x -24.3 y 217.3.(-20.7, 185) moving=true→(-24.3, 217.3)with its camera following (main,-11,218→main,-24,337); the server's own store went(2.4,-21.2)→(-24.3,217.3)= 240 px,arrived=true.Stopped→stopped · clean exit (code 0),/statusrunning:false pid:null./rust-code?serve=…on the same :8091: panelrust-code, polling,al serve is up, 18 hooks, 24 080 chars.code_active=true, runner frame up); switching to RUST hides it and stops the panel's poll; closing CODE closes both and stops the poll.The one bug the live run found (
bb4b06a)js/game.jsrewrites the address bar as soon as the character loads (history.replaceState({}, title, "/character/<name>/in/…"), ~line 1608; the auto-reload navigates to that URL). Because the pane mounts lazily,location.searchwas already empty whenresolve_endpointran — the player's?serve=http://127.0.0.1:8212fell out of the chain and the panel quietly started polling the shipped default :8192: a differental serve, with a different crate allow-list and a different character's credentials. Fix: capturelocation.searchwhen the script loads and use it when the live search no longer answers the parameter. A host that never touches its URL behaves exactly as before (live search still wins), and the chain's order is unchanged. Two tests pin both halves.Tests
node/test/rust_serve_panel.test.js,node/test/rust_code_page.test.js) — 35 from the previous session + 2 for the URL rewrite;node --test node/test/: 210 pass / 0 fail;/sourceleaves the editor alone, and nothing (source, crate list, build log) passes through the backend.node_modules(symlinks, untracked) and the auto-bumpedversion.jsare not included.hello/src/main.rsis byte-identical — Compile wrote back what it was given (main.rs.goodisal serve's own backup, untracked).Note for #17 (T5-1), not a T6-2 bug
hello'starget_fromwalks towardmain's origin from wherever the character stands; from some spots that line crosses a wall, the server jails him on the first leg (#C cheater: Rusty … at main), and from jail every further leg answersnew_map. That is the jail-safe travel redesign #17 owns. A run starting on walkable ground (main(3,-30),main(-23,209)) arrives cleanly.Driving the real page (headless chromium against the worktree backend on :8091, `al serve` on :8212) found the one thing the Node suite could not see: the RUST pane mounts lazily on tab click, but js/game.js rewrites the address bar the moment the character loads (`history.replaceState({}, title, "/character/<name>/in/…")`, and the auto-reload navigates to that URL). So `location.search` was empty by the time `resolve_endpoint` ran, the player's `?serve=http://127.0.0.1:8212` fell out of the chain, and the panel started polling the shipped default :8192 — a different `al serve`, with a different crate allow-list and a different character's credentials. Fix: capture `location.search` when the script loads and use it when the live search no longer answers the parameter. A host that never touches its URL behaves exactly as before (live search still wins), and nothing in the chain's order changes. Two tests in node/test/rust_serve_panel.test.js pin it: the override survives a load → rewrite → click sequence, and no `?serve=` at load leaves the rest of the chain in charge (a stored edit still wins, no phantom value is invented). Suite for the panel: 37 pass / 0 fail; full node suite: 210 pass / 0 fail.The entry carries the hour (2026-09-24 12:00 local) and, per the log's habit, the things a later session would otherwise rediscover the slow way: * the live evidence, in coordinates: Compile (`Finished release profile in 2.85s`) → Run (`pid 2630111`, `hello: logged in … as "Rusty"`) → the log pane streaming `x 2.4 y -21.2` → `x -11.0 y 98.1` → `x -24.3 y 217.3` → Stop (`clean exit (code 0)`), with a second tab watching Rusty through the game's own observer (`moving=true` at (-20.7,185), camera following) and the server's store confirming the 240 px move; * the one real bug the live run found and what fixed it (the game page's `history.replaceState` erasing `?serve=` before the lazy mount reads it); * two operational traps: the server refuses a second login for a character it still holds (`ex("ingame")` for 120 min on `last_sync`), so a driver that leaves its tab open poisons the next run — hence the driver's preflight; and `hello`'s toward-origin walk jails the character from some spawn points, which is #17's jail-safe travel work, not a panel bug. Panel suite 37/37, full node suite 210/210. PLAN: #38 `[x]`.Two more commits after review-relevant testing, both from re-running the flow by hand rather than from the fake DOM.
ab0fd65— Compile no longer truncates the crate.al serve'sPOST /compilewrites the body it is given to<crate>/src/main.rsbefore cargo runs, and an empty source is a legal thing to send a compiler, so nothing refused it. An empty editor is the ordinary shape of a bad load —load_sourcedeliberately leaves the box alone when/sourceanswersok:false(crate off that endpoint's allow-list, endpoint moved under the tab) — so "something went wrong on the way in" plus one Compile equals the player's crate at 0 bytes.src/main.rs.goodrecovers it byte-exactly, but only if a build has succeeded once: the copy is written on success.This happened to me during the pass below: a hand-rolled
curl -X POST /compiletruncatedexamples/hello/src/main.rsto 0 bytes. Restored from git (24116 bytes,git diffempty).The refusal belongs on the side that knows whether a human meant it, because the endpoint's contract is T4-3's and already merged (#16): Compile — and Reload, whose pre-restart compile reaches the same write — refuses an empty or whitespace-only editor, claims the log pane to say why, shows the Restore row (
{"restore": true}is the honest way to replace a file), and parks the note ondisk-state, the slot the 1.5 s poll never reclaims. Loaded crates and a clean-box Reload behave exactly as before. Still open, deliberately: the endpoint itself will take the same body from curl or an IDE bridge and truncate; a whitespace-onlysourcerefusal incontrol.rs::compile(unlessrestore) is the companion fix, and nothing incrates/al-tool/tests/covers an empty source either. Say the word and I'll do it here or in a follow-up issue.Live check on :8091 + :8212 (
/tmp/guard.js, no game login → no character conflict): emptied box → Compile →Compile refused,al serve's own ring byte-identical across the click (so no build ran),main.rsstill 24116 bytes → whitespace box → same refusal → Restore →[al] built …/target/release/hello, editor refilled fromon_disk→ reload → Compile on the loaded crate builds normally.c473be1— main merged in, because the PR reported itself unmergeable once T5-1 landed. One conflict: both sides put a newest-first entry at the top ofrust/PROGRESS.md; both kept, no content edited.Panel suite 38/38, full
node --test node/test/211/211,cargo fmt --checkclean on the merged tree. Driver note for whoever re-runs the browser flow next: Chromium serves/js/*.js?v=…from memory across navigations in one target — the first pass exercised the previous client untilNetwork.setCacheDisabled.