Files
galaxy-game/ui
Ilia Denisov ce7a66b3e6 ui/phase-11: map wired to live game state
Replaces the Phase 10 map stub with live planet rendering driven by
`user.games.report`, and wires the header turn counter to the same
data. Phase 11's frontend sits on a per-game `GameStateStore` that
lives in `lib/game-state.svelte.ts`: the in-game shell layout
instantiates one per game, exposes it through Svelte context, and
disposes it on remount. The store discovers the game's current turn
through `lobby.my.games.list`, fetches the matching report, and
exposes a TS-friendly snapshot to the header turn counter, the map
view, and the inspector / order / calculator tabs that later phases
will plug onto the same instance.

The pipeline forced one cross-stage decision: the user surface needs
the current turn number to know which report to fetch, but
`GameSummary` did not expose it. Phase 11 extends the lobby
catalogue (FB schema, transcoder, Go model, backend
gameSummaryWire, gateway decoders, openapi, TS bindings,
api/lobby.ts) with `current_turn:int32`. The data was already
tracked in backend's `RuntimeSnapshot.CurrentTurn`; surfacing it is
a wire change only. Two alternatives were rejected: a brand-new
`user.games.state` message (full wire-flow for one field) and
hard-coding `turn=0` (works for the dev sandbox, which never
advances past zero, but renders the initial state for any real
game). The change crosses Phase 8's already-shipped catalogue per
the project's "decisions baked back into the live plan" rule —
existing tests and fixtures are updated in the same patch.

The state binding lives in `map/state-binding.ts::reportToWorld`:
one Point primitive per planet across all four kinds (local /
other / uninhabited / unidentified) with distinct fill colours,
fill alphas, and point radii so the user can tell them apart at a
glance. The planet engine number is reused as the primitive id so
a hit-test result resolves directly to a planet without an extra
lookup table. Zero-planet reports yield a well-formed empty world;
malformed dimensions fall back to 1×1 so a bad report cannot crash
the renderer.

The map view's mount effect creates the renderer once and skips
re-mount on no-op refreshes (same turn, same wrap mode); a turn
change or wrap-mode flip disposes and recreates it. The renderer's
external API does not yet expose `setWorld`; Phase 24 / 34 will
extract it once high-frequency updates land. The store installs a
`visibilitychange` listener that calls `refresh()` when the tab
regains focus.

Wrap-mode preference uses `Cache` namespace `game-prefs`, key
`<gameId>/wrap-mode`, default `torus`. Phase 11 reads through
`store.wrapMode`; Phase 29 wires the toggle UI on top of
`setWrapMode`.

Tests: Vitest unit coverage for `reportToWorld` (every kind,
ids, styling, empty / zero-dimension edges, priority order) and
for the store lifecycle (init success, missing-membership error,
forbidden-result error, `setTurn`, wrap-mode persistence across
instances, `failBootstrap`). Playwright e2e mocks the gateway for
`lobby.my.games.list` and `user.games.report` and asserts the
live data path: turn counter shows the reported turn,
`active-view-map` flips to `data-status="ready"`, and
`data-planet-count` matches the fixture count. The zero-planet
regression and the missing-membership error path are covered.

Phase 11 status stays `pending` in `ui/PLAN.md` until the local-ci
run lands green; flipping to `done` follows in the next commit per
the per-stage CI gate in `CLAUDE.md`.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-08 21:17:17 +02:00
..
2026-05-07 09:40:37 +02:00
2026-05-07 13:41:33 +02:00
2026-05-07 07:18:55 +02:00

ui — Galaxy Cross-Platform Client

ui/ hosts the new cross-platform Galaxy client. A single TypeScript + Svelte source tree builds to five targets: web, web-mobile, standalone PC (mac/win/linux), iOS, and Android. A shared Go module (ui/core) carries envelope cryptography, the FlatBuffers codec, keypair management, and a thin bridge over pkg/calc/ for UI-side game math; it is compiled to WASM for the web targets, gomobile native libraries for mobile, and embedded directly in Wails on desktop. All network I/O lives on the TypeScript side via ConnectRPC, so the Go module is a pure compute boundary on every platform.

The legacy Fyne client under client/ is reference-only. Nothing in ui/ imports from it.

The full staged implementation plan lives in PLAN.md. The strategic rationale (why Svelte, why PixiJS, why Go-as-WASM, why Wails+Capacitor) lives outside the repo at ~/.claude/plans/buzzing-questing-fountain.md. This README is a quick orientation; deeper per-phase design notes earn their place under ui/docs/ as they are introduced.

Targets

Target Wrapper Toolchain Phase
web browser tab Vite + WASM 5+
web-mobile mobile browser Vite + WASM 5+
desktop (mac) Wails v2 Go + Wails CLI 31
desktop (win) Wails v2 Go + Wails CLI 31
desktop (linux) Wails v2 Go + Wails CLI 31
iOS Capacitor gomobile + Xcode 32+
Android Capacitor gomobile + Gradle 32+

Layered architecture

  • TypeScript + Svelte 5 frontend, shared across all five targets, scaffolded with SvelteKit + Vite.
  • PixiJS v8 with dual WebGPU/WebGL backend for the world map renderer.
  • Go module ui/core/ as a compute-only library (canonical bytes, Ed25519 sign/verify, FlatBuffers codec, keypair, thin bridge to pkg/calc/) compiled to WASM, gomobile, and Wails-embedded native.
  • TypeScript-side Core interface with three adapters (WasmCore, WailsCore, CapacitorCore) selected at build time.
  • GalaxyClient on top of Core performs all network I/O via ConnectRPC (@connectrpc/connect-web) on every platform.
  • Per-platform storage: WebCrypto + IndexedDB on web, OS keychain
    • SQLite on desktop, iOS Keychain / Android Keystore + SQLite on mobile, all behind a single KeyStore and Cache TypeScript interface.
  • Mobile-first navigation: one active view occupies the main area at a time; the sidebar holds a single tool (calculator, inspector, or order) with persistent state on switch.

Repository layout

ui/
├── PLAN.md                 staged implementation plan (Phases 1-36)
├── Makefile                wasm / ts-protos / web / mobile / desktop targets
├── README.md               this file
├── buf.gen.yaml            local-plugin TS Protobuf-ES generator
├── docs/                    topic-based design notes
│   ├── auth-flow.md         email-code login, session store, revocation
│   ├── i18n.md              translation primitive, native-name picker, extensibility
│   ├── storage.md           web KeyStore/Cache, IDB schema, baseline
│   ├── testing.md           per-PR / release test tiers
│   └── wasm-toolchain.md    TinyGo build, JSDOM loading, bundle budget
├── core/                    ui/core Go module (canonical bytes, keypair)
├── wasm/                    TinyGo entry point exposing Core to JS
└── frontend/                SvelteKit / Vite source
    ├── src/api/             GalaxyClient + typed Connect client + auth + session
    ├── src/lib/             env config, session store, revocation watcher
    ├── src/platform/core/   Core interface + WasmCore adapter
    ├── src/platform/store/  KeyStore/Cache interfaces + web adapter
    ├── src/proto/           generated Protobuf-ES + Connect descriptors + FlatBuffers TS bindings
    ├── src/routes/          SvelteKit routes (/, /login, /lobby, /lobby/create)
    └── static/              core.wasm + wasm_exec.js (committed artefacts)

Linked topic docs:

  • docs/auth-flow.md — email-code login, session store state machine, revocation watcher.
  • docs/lobby.md — lobby UI sections, application / invite lifecycle, create-game form defaults.
  • docs/i18n.md — translation primitive, native-name language picker, recipe for adding a new locale.
  • docs/storage.md — web KeyStore/Cache, IndexedDB schema, browser baseline.
  • docs/wasm-toolchain.md — TinyGo build, loading recipe, bundle size budget.
  • docs/testing.md — Tier 1 per-PR + Tier 2 release test tiers.
ui/
├── README.md                 this file
├── PLAN.md                   staged implementation plan
├── Makefile                  cross-target build placeholders
├── pnpm-workspace.yaml       pnpm workspace root
├── .gitignore
├── docs/                     per-phase topic docs (added per phase)
├── frontend/                 TS + Svelte source, shared across targets
├── core/                     Go module ui/core (Phase 3+)
├── wasm/                     TinyGo entry point for core.wasm (Phase 5)
├── mobile-bridge/            gomobile bindings (Phase 32+)
├── desktop/                  Wails project (Phase 31)
├── mobile/                   Capacitor project (Phase 32+)
└── web/                      static deploy assets (Phase 30+)

Build pipeline

Every cross-target build flows through make at this level. All named targets are placeholders until the named phase lands; running make with no arguments prints the current placeholder map.

make web             Vite production build                      Phase 5+
make wasm            TinyGo → core.wasm                         Phase 5
make ts-protos       Connect-ES + Protobuf-ES gen               Phase 5
make fbs-ts          FlatBuffers TS bindings via flatc          Phase 8
make gomobile        gomobile bind → ios + android              Phase 32+
make desktop-mac     Wails build for darwin                     Phase 31
make desktop-win     Wails build for windows                    Phase 31
make desktop-linux   Wails build for linux                      Phase 31
make ios             Capacitor + xcodebuild                     Phase 32+
make android         Capacitor + gradle                         Phase 32+
make all             every target above

Local development

For UI work against a real stack, the tools/local-dev/ docker compose brings up postgres + redis + mailpit + backend + gateway in one command, and ui/frontend/.env.development is already wired to talk to it:

make -C tools/local-dev up        # build + start, wait for healthy
pnpm -C ui/frontend dev           # Vite on the host
# UI:     http://localhost:5173
# Mailpit: http://localhost:8025

The stack accepts a fixed dev code (123456) in addition to the real Mailpit-delivered one. Full runbook in ../tools/local-dev/README.md.

Per-phase docs

Topic docs live under ui/docs/ and are added per phase as they're needed (testing tiers, WASM toolchain, navigation shell, renderer internals, sync protocol, auth flow, and so on). The staged plan in PLAN.md names the topic doc each phase produces.

Cross-references

  • PLAN.md — staged implementation plan with goals, artifacts, dependencies, acceptance criteria, and targeted tests per phase.
  • ../docs/ARCHITECTURE.md — platform architecture and the transport security model (§15) the client envelope contract derives from.
  • ../docs/FUNCTIONAL.md — per-domain user stories that drive the UI flows.
  • ../docs/TESTING.md — project-wide testing layers; UI-specific test tiers (Vitest, Playwright) live in ui/docs/testing.md from Phase 2 onward.