**Final report — phase 07 final verification pass** - **Verified:** all 5 tasks present in `complete/`; app boots (`/healthz` 200, `/` 200, static + atlas + `/api/levels` served); `docs/CLIENT.md` module map matches the on-disk `app/static/` tree; no code defects found. - **Tests/lint/coverage:** `bash .agents/validate.sh` → ruff clean, pyright 0 errors, `undercooked-levels validate` pass, **full pytest 1838 passed, 97.21% coverage (floor 90%)**, slow sim stage 2 passed, repo hygiene clean. `uv run pytest tests/e2e -q` → **53 passed** (all 8 suites added this phase). - **Criteria:** (1) two tabs create→join→ready→start→serve one dish, keyboard only — green via `test_us01/02/03/04` ✔; (2) e2e green ✔; (3) canvas non-blank, pixels change after a move — `test_us04_render_smoke` ✔; (4) HUD score within 200 ms of the `served` event — asserted, green ✔; (5) zero console errors + zero off-origin requests — `test_atlas_loads` / `test_us01_landing_surface` ✔; (6) `docs/CLIENT.md` matches layout ✔; (7) 100% suite + 97.21% ✔; (8) docs pass below ✔; (9) no regression in completed phases (all prior suites green) ✔. - **README.md fixed:** status still said "next pending phase is 07" → now 07 complete, 08 pending; the phase-07 client text buried in the phase-05 bullet → its own "canvas client, art, HUD (phase 07)" bullet; stale "What does not exist yet" (claimed movement/controls missing) → rewritten to phases 08–14; "once the room flow ships" → "live in the browser today"; Layout `app/static/` "renderer arrives with the rest of phase 07" → full shipped-client map; Layout `docs/` "CLIENT lands with phase 07" → `CLIENT.md` listed; added `gen_art_index.py` Layout entry. - **AGENTS.md fixed:** "the eight screens" (7 listed) → "the seven screens" (+ `dom.js` is the shared builder, not a screen); e2e naming line → notes the non-story guard `test_atlas_loads.py`. - **Decisions:** no code changes were needed; only the two doc fixes above. Docs re-verified by `test_docs_commands`/`test_docs_index` (44 passed) plus the final full gate. - **Next pending phase:** `08_client_prediction_and_touch`.
95 lines
2.8 KiB
JavaScript
95 lines
2.8 KiB
JavaScript
// Undercooked — the play screen (phase 07 task 02).
|
|
//
|
|
// The kitchen's frame: the canvas host (`#kcanvas` — hidden until the
|
|
// task-04 renderer sizes and unhides it), the HUD root (`#hud`, filled
|
|
// by task 04), the *Leave* control, the reconnecting overlay (its
|
|
// *logic* lands in phase 08; this task shows the sign), the emote row
|
|
// (the `docs/PROTOCOL.md` whitelist: `wave`, `cheer`, `thumbs_up`,
|
|
// `thumbs_down`, `angry`, `star`), the spectator notice (a join during
|
|
// `COUNTDOWN`/`PLAYING`/`ROUND_END` watches, per the wire contract) and
|
|
// the banner region (non-fatal `err` codes, e.g. a `start` refusal).
|
|
|
|
import { el, frag } from "./dom.js";
|
|
|
|
/** The emote whitelist (docs/PROTOCOL.md "EmoteMsg"): id → label. */
|
|
const EMOTES = [
|
|
["wave", "Wave", "👋"],
|
|
["cheer", "Cheer", "🎉"],
|
|
["thumbs_up", "Thumbs up", "👍"],
|
|
["thumbs_down", "Thumbs down", "👎"],
|
|
["angry", "Angry", "😠"],
|
|
["star", "Star", "⭐"],
|
|
];
|
|
|
|
let overlay = null;
|
|
let bannerEl = null;
|
|
|
|
export function mount(target, _data, app) {
|
|
overlay = el(
|
|
"div",
|
|
{ id: "reconnect-overlay", class: "overlay", hidden: true, role: "status" },
|
|
el("p", {}, "Connection lost — reconnecting… (phase 08)"),
|
|
);
|
|
bannerEl = el("p", {
|
|
id: "play-banner",
|
|
class: "status",
|
|
role: "status",
|
|
"aria-live": "polite",
|
|
hidden: true,
|
|
});
|
|
target.append(
|
|
el("div", { class: "play-screen" },
|
|
el("div", { class: "play-head" },
|
|
el("p", {
|
|
id: "spectator-note",
|
|
class: "hint",
|
|
hidden: !app.spectator,
|
|
}, "You are watching — the room is full."),
|
|
el("button", {
|
|
id: "leave-btn",
|
|
class: "btn",
|
|
type: "button",
|
|
onClick: () => app.leave(),
|
|
}, "Leave"),
|
|
),
|
|
bannerEl,
|
|
el("canvas", { id: "kcanvas", hidden: true, "aria-hidden": "true" }),
|
|
el("div", { id: "hud", class: "hud" }),
|
|
el("div", { class: "emote-row", role: "group", "aria-label": "Emotes" },
|
|
frag(
|
|
...EMOTES.map(([id, label, glyph]) =>
|
|
el(
|
|
"button",
|
|
{
|
|
class: "btn emote",
|
|
type: "button",
|
|
"aria-label": label,
|
|
"data-emote": id,
|
|
onClick: () => app.emote(id),
|
|
},
|
|
el("span", { "aria-hidden": "true" }, glyph),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
overlay,
|
|
),
|
|
);
|
|
}
|
|
|
|
/** The reconnecting overlay (populated in phase 08). */
|
|
export function showReconnecting(visible) {
|
|
if (overlay !== null) overlay.hidden = !visible;
|
|
}
|
|
|
|
export function setBanner(message) {
|
|
if (bannerEl === null) return;
|
|
bannerEl.textContent = message ?? "";
|
|
bannerEl.hidden = message === null || message === "";
|
|
}
|
|
|
|
export function unmount() {
|
|
overlay = null;
|
|
bannerEl = null;
|
|
}
|