3471d40576
CI / changes (pull_request) Successful in 3s
CI / unit (pull_request) Successful in 9s
CI / integration (pull_request) Successful in 16s
CI / ui (pull_request) Successful in 1m4s
CI / conformance (pull_request) Successful in 9s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 1m27s
The finished-game export now works identically on every platform: the client mints a signed relative URL (game.export_url) carrying its date locale, IANA time zone and localized non-play labels, resolves it against its own origin and hands it to the platform's native download — Telegram downloadFile, VKWebAppDownloadFile, or a plain browser anchor (also the desktop VK iframe). Both artifacts ride the route: the .gcg text (no more clipboard mode, except the legacy pre-8.0 Telegram fallback) and the PNG of the final position. The PNG is rasterized by a new internal 'renderer' sidecar (node:22-slim + skia-canvas + baked Liberation/Noto Color Emoji fonts) executing the SAME ui/src/lib/gameimage.ts the web project unit-tests, bundled at image build time — one renderer, no drift; the browser no longer draws or delivers bytes itself. The backend rebuilds the render payload from the journal + engine.AlphabetTable, verifies the HMAC (10-minute TTL, BACKEND_EXPORT_SIGN_KEY, constant-time, uniform 404s) on its public group, and streams the artifact as a named attachment; the gateway forwards /dl/* behind the per-IP public rate limiter (caddy @gateway matcher extended — the landing catch-all trap). Deploy: renderer service (compose + prod overlay + roll before backend + prod push list), EXPORT_SIGN_KEY env (TEST_/PROD_ secrets), CI runs the sidecar smoke in the ui job. Docs: ARCHITECTURE export-delivery section, FUNCTIONAL(+_ru), UI_DESIGN, TESTING, deploy/README.
35 lines
1.8 KiB
Markdown
35 lines
1.8 KiB
Markdown
# renderer — the finished-game image-render sidecar
|
|
|
|
An internal-only Node service that rasterizes the finished-game export PNG. It runs the
|
|
**same** `ui/src/lib/gameimage.ts` the web project unit-tests — bundled verbatim at image
|
|
build time (`src/entry.ts` → esbuild → `dist/gameimage.mjs`) — on
|
|
[skia-canvas](https://github.com/samizdatco/skia-canvas), so the server render is
|
|
pixel-identical to the design the owner signed off in the browser.
|
|
|
|
## Interface
|
|
|
|
- `POST /render` — `{game, moves, alphabet, labels, dateLocale, hostname, scale?}` →
|
|
`image/png`. `game`/`moves` are the ui-model shapes (`GameView` / `MoveRecord[]`);
|
|
`alphabet` is the per-variant `(index, letter, value)` table tile values are drawn
|
|
from; `labels` localizes the non-play moves (pass/exchange/resign/timeout);
|
|
`hostname` + `dateLocale` feed the footer.
|
|
- `GET /healthz` — liveness (the compose healthcheck the backend's `depends_on` gates on).
|
|
|
|
The service draws and nothing else: authentication, the participant check and the signed
|
|
public download URL all live in the backend (`backend/internal/server/export.go`); the
|
|
network path is client → gateway `/dl/*` → backend → this sidecar.
|
|
|
|
## Development
|
|
|
|
```sh
|
|
pnpm install # skia-canvas + esbuild MUST stay approved in pnpm-workspace.yaml
|
|
# (allowBuilds) or the native binary is silently never fetched
|
|
pnpm test # bundles, then node --test against testdata/request.json
|
|
node src/server.mjs # local run on :8090 (RENDERER_PORT overrides)
|
|
```
|
|
|
|
The runtime image (`renderer/Dockerfile`, `node:22-slim`) bakes in Liberation Sans (the
|
|
UI font stack's Linux face) and Noto Color Emoji (the scoresheet 🏆); skia-canvas picks
|
|
both up through fontconfig. The fixture `testdata/request.json` is a real 35-move
|
|
self-played Russian Scrabble game.
|