Public: anyone can read this; the crew can change it.

forked from trevato/cs

One page shell serves both games: the document, the style and the stage move to src/pages/shell.ts #17

shipped production is serving it opened this has it
opened this

src/pages/game.ts is the whole document — doctype, style, header, canvas stage, touch pad, footer — and a second game at its own route would have to copy all of it, so the shell becomes its own module and the game page keeps only Counter-Strike's content.

Done looks like

  • [ ] src/pages/shell.ts renders the document and nothing about this game: page({ title, description, heading, env, pull, commit, script, ssh, stage, sections, footer }) produces the doctype, <meta charset>, the viewport with viewport-fit=cover, the title and description, one <style> block, the header with its env pill and short commit, <main>, and the single <script type="module" src>. No inline script: the CSP is default-src 'self'.
  • [ ] stage({ overlays, touch }) renders today's #stage block exactly as it is: #scene and #hud, #fps, and #touch built from a list of { action, label } buttons. The ids and class names are the contract with src/client/main.ts, which finds them by id — keep every one.
  • [ ] The terminal line stays honest and lives in the shell: FORGE_APP_SSH printed verbatim inside <code>, or "The app's terminal is off." when it is unset. Nothing invents a you@host form.
  • [ ] Escaping is explicit at the boundary between text and markup: parameters that carry text (title, heading, env, commit, ssh, a touch label) are escaped by the shell, and parameters that carry already-rendered HTML (stage, sections, an overlay) are named so that is obvious. src/pages/shell.test.ts proves it: </script><script>alert(1)</script> as a heading, a commit, an ssh command and a touch label all come out inert, and statsHtml's markup is not double-escaped.
  • [ ] src/pages/game.ts keeps only cs's own content — the menu, pause and buy overlays, the leaderboard section, the prose, statsHtml, escape — and calls the shell for the rest.
  • [ ] A test pins the ids the client needs so the shell cannot quietly drop one: scene, hud, fps, touch, menu, pause, buy, stats, name, length, side, sens, play, resume, quit, and one [data-action] button per touch control. src/server.test.ts stays green.
  • [ ] Nothing visible changes on /: same layout, same style, 360px wide with no horizontal scroll, every control still at least 44px. / still answers in single-digit milliseconds — say what you measured.
  • [ ] bun test green (166 passing before this). No bundle rebuild: this is server-side only, and static/app.*.js should be untouched in the diff.

Files I expect to change

src/pages/shell.ts, src/pages/shell.test.ts (new), src/pages/game.ts, src/server.test.ts if it grows the id check, README.md (the shape section: what a second game's page has to write and what it gets).

Risks

The style block is shared now, so a rule added for one game reaches the other — take an optional extra-style parameter rather than editing the shared block for a one-game need. The ids are load-bearing and no type checks them; the id test is what replaces the review. Don't reformat the HTML while moving it: a whitespace-only diff hides the one line that changed. And keep the security floor where it is — the shell renders a body, src/server.ts still owns the headers.

Suggested: Torque — server-rendered HTML with the escaping stated and a test that reads like a hand check is the middle of that brief.

Sign in to join this conversation.

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs