# Hello World to authenticated multiplayer

A progressive tutorial using **Redweb 0.14.0**, server-rendered **TSX**, and **redweb-client 0.2.0**. No React, no socket decorators, and no second HTTP listener. Read the guided chapters at https://redweb.magnisolution.com/docs/tutorial.

## Run the checkpoints

Use Node **22.13 or newer** (SQLite is experimental on Node 22), npm, and a maintained Node release for deployment. From this directory:

```sh
npm ci
npm run hello
```

Open http://localhost:8181. Stop the process with Ctrl+C before starting another checkpoint on the same port.

```sh
npm run counter
```

Open two tabs and click either counter. The number lives on the server. `@state()` binds the TSX expression automatically; no `<output>` or `data-rw-state` boilerplate is needed.

For the finished game:

```sh
npm run build
npm run add-user -- alice
npm run add-user -- bob
npm start
```

Save the randomly generated passwords displayed by provisioning. There are no default passwords. Open **http://localhost:8181/login**, using two browser profiles or one regular and one private window. Sign in as different accounts, then join the same room name. The first player is X; the second is O. Names are public lobby identifiers, **not private invitations**. Take turns clicking squares. Refresh, sign in again, and rejoin the same room to recover your seat and board. A transient socket reconnect automatically resumes; uncertain moves are never queued or replayed.

## How the files fit

- `src/hello.tsx` and `src/counter.tsx` are complete, minimal executable checkpoints.
- `src/http-ws.tsx` is the homepage's optional combined HTTP/socket example. After building, run `node dist/http-ws.js` with no other checkpoint using port 8181. Visit `/` over HTTP and send `{"type":"hello"}` to `ws://localhost:8181/chat`.
- `src/server.tsx` composes TSX pages, `/match`, and the account lifecycle with `defineApp`. Its `gameApp` factory gives tests isolated stores and ports; it is not a required Redweb entry-point convention.
- `src/app.tsx` is the game process entry point; `await app.run()` owns startup.
- `src/game.ts` contains synchronous game rules and bounded rooms, independently unit-tested.
- `src/contract.ts` declares `join`, `move`, `resume`, `state`, and `notice` payloads once.
- `src/routes.ts` provides one handler per incoming message type. Clients cannot choose their identity, mark, turn, or board.
- `src/client.ts` uses `redweb-client` for connections, subscriptions and reconnects. A small adapter displays server snapshots in the TSX-rendered board. The page is `live: false` because this game deliberately uses `/match`, not an additional live-page socket.
- `src/shared/auth.ts` and `store.ts` are generated from the immutable Redweb 0.14.0 dashboard recipe; the only adaptation is an ESM `.js` import extension. Do not edit them independently. The store also includes the starter's unused card methods/tables; they are not gameplay state. This reuses the tested sign-in implementation instead of inventing a second password/session system.

## Optional configuration

The introductory examples intentionally use `defineApp({ pages: [...] })` and `await app.run()`. This project declares `"type": "module"` and extends `redweb/tsconfig.json`, so top-level await and Redweb JSX compile without React. Add options when you need them:

```tsx
const app = defineApp({
    pages: [HomePage, AboutPage],
    sockets: [MatchRoute],
    services: [Accounts],
    port: 8181,
    templateRoot: '/absolute/path/to/templates',
});

await app.run();
```

Those class names are illustrative; the complete game composition is in `server.tsx`. `templateRoot` is for relative template/CSS files, not required by the inline introductory pages. `signals` defaults to true; tests use `signals: false` and own `shutdown()`. Ordinary Promise error handling remains available without repeating it in every introduction.

For this game's configuration, set `PORT`, `GAME_DATABASE` (default `data/game.sqlite`), and `GAME_ORIGIN` (an exact HTTP(S) origin with no trailing slash). With the default origin, use **localhost**, not 127.0.0.1, in the browser. In production set `NODE_ENV=production` and `GAME_ORIGIN=https://your-domain.example`, use a local HTTPS reverse proxy to the loopback listener, and protect the SQLite volume and backups. Never serve the database or source secrets as public assets.

## Tests and honest limits

```sh
npm test
npm run test:coverage
npm run test:browser
```

The browser command uses installed **Google Chrome in headed mode** and two isolated cookie contexts. Unit tests cover all winning lines, draws, turn/ownership errors, bounds, stale revisions, and room capacity. Integration tests use actual HTTP, WebSockets, SQLite and passwords; no transport mocks. Tests have deadlines for failures, not soak windows. The coverage command currently enforces **100% for the game-rule module**, not a misleading 100% for the browser bundle, reused authentication module, or the entire project.

This is a teaching app, not a production identity service or durable game platform. Accounts and sessions persist in SQLite; matches are **in-memory, single-process**, capped at 100 rooms and 100 connections, and disappear on restart. Seats stay reserved until restart; there is no rematch, matchmaking, spectators, abandoned-room eviction, cross-worker fan-out, or account recovery. Logout closes that account's connected game sockets, and every command and fan-out rechecks its session. Add an identity provider, durable match storage, explicit room invitations, expiry notifications, proxy abuse controls and operational monitoring before exposing a real product. See the dashboard recipe for its password, session, and rate-limit details.
