Three-stage refactor of the game-engine plumbing (game logic untouched): Stage 1 — lock-free persistence + admin serialisation. Remove the file lock from repo/fs (the .lock file, the Read/Write-vs-*Safe duality and the dead ReadSafe polling) and replace the two-step rename with a single atomic rename so concurrent reads are torn-free without a lock. Serialise the state-mutating admin writers (init/turn/banish) with one shared router LimitMiddleware, rewritten to block on the request context instead of a racy shared 100ms timer. Stage 2 — remove the obsolete immediate-command path end to end. Players submit through PUT /api/v1/order; the legacy PUT /api/v1/command path is deleted across game (route, handler, 24 command factories, Ctrl), backend (Commands handler/route, engineclient.ExecuteCommands), gateway (dispatch + executeUserGamesCommand + routing entry), the FlatBuffers/model contract (UserGamesCommand[Response]) and transcoder, plus every affected OpenAPI/README/FUNCTIONAL/ARCHITECTURE doc. The integration proxy test is converted to the order path. Stage 3 — flatten the REST->engine wrapper. Replace the executor adapter, the controller package functions and RepoController with one concrete controller.Service; drop the single-implementation Repo and Storage interfaces (repo.Repo / fs.FS are now concrete). Handlers depend on a thin handler.Engine seam and own the domain->REST projection; storage is resolved once at startup instead of per request. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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 web target is a finalized, installable PWA: a shared design-token system with light/dark themes, WCAG 2.2 AA accessibility, en/ru localisation with a persisted choice, a central error surface, and offline tolerance — see the topic docs below.
The legacy Fyne client under client/ is reference-only.
Nothing in ui/ imports from it.
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 design notes live under ui/docs/.
Targets
| Target | Wrapper | Toolchain | Status |
|---|---|---|---|
| web | browser tab | Vite + WASM | implemented |
| web-mobile | mobile browser | Vite + WASM | implemented |
| desktop (mac) | Wails v2 | Go + Wails CLI | planned (see ROADMAP.md) |
| desktop (win) | Wails v2 | Go + Wails CLI | planned (see ROADMAP.md) |
| desktop (linux) | Wails v2 | Go + Wails CLI | planned (see ROADMAP.md) |
| iOS | Capacitor | gomobile + Xcode | planned (see ROADMAP.md) |
| Android | Capacitor | gomobile + Gradle | planned (see ROADMAP.md) |
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 topkg/calc/) compiled to WASM, gomobile, and Wails-embedded native. - TypeScript-side
Coreinterface with three adapters (WasmCore,WailsCore,CapacitorCore) selected at build time. GalaxyClienton top ofCoreperforms 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
KeyStoreandCacheTypeScript interface.
- SQLite on desktop, iOS Keychain / Android Keystore + SQLite on
mobile, all behind a single
- Single-URL app-shell navigation: the game UI is one route served
at
/game/; the screen (login / lobby / game) and the in-game view are in-memory state (lib/app-nav.svelte.ts), not URLs, so the address bar never changes. Browser Back/Forward move between screens via shallow routing without touching the URL — a model that also suits the bundled standalone targets (Wails / Capacitor) that have no URLs. 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. Seedocs/navigation.md.
Repository layout
ui/
├── PLAN.md staged implementation plan
├── ROADMAP.md planned desktop / mobile / multi-turn features
├── PLAN-finalize.md PWA, accessibility, localisation, error UX
├── 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
│ ├── order-composer.md order draft model, persistence, history-mode wiring
│ ├── 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
├── mobile-bridge/ gomobile bindings (planned — see ROADMAP.md)
├── desktop/ Wails project (planned — see ROADMAP.md)
├── mobile/ Capacitor project (planned — see ROADMAP.md)
└── frontend/ SvelteKit / Vite source
├── src/api/ GalaxyClient + typed Connect client + auth + session
├── src/lib/ app-shell nav + screens + game shell, env config, session store, stores
├── 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/ single-URL app-shell: `/game/` dispatcher (+page.svelte) + `/__debug/*`
└── static/ core.wasm + wasm_exec.js (built by `make wasm` / CI; gitignored)
Linked topic docs:
docs/navigation.md— single-URL app-shell, screens and views as in-memory state, screen history, sidebar tools.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/order-composer.md— local order draft store, persistence, history-mode wiring.docs/wasm-toolchain.md— TinyGo build, loading recipe, bundle size budget.docs/design-system.md— design tokens, light/dark theming, component conventions.docs/a11y.md— WCAG 2.2 AA approach, axe + keyboard gates, shared a11y primitives.docs/error-state-ux.md— central error surface, shared loading/empty/error states, selection marker, sheet dismissal.docs/pwa-strategy.md— installable, offline PWA: service worker, manifest, icons.docs/testing.md— Tier 1 per-PR + Tier 2 release test tiers.
Build pipeline
Every cross-target build flows through make at this level.
Native targets are placeholders until their platform work lands;
running make with no arguments prints the current placeholder map.
make web Vite production build
make wasm TinyGo → core.wasm
make ts-protos Connect-ES + Protobuf-ES gen
make fbs-ts FlatBuffers TS bindings via flatc
make gomobile gomobile bind → ios + android (planned — see ROADMAP.md)
make desktop-mac Wails build for darwin (planned — see ROADMAP.md)
make desktop-win Wails build for windows (planned — see ROADMAP.md)
make desktop-linux Wails build for linux (planned — see ROADMAP.md)
make ios Capacitor + xcodebuild (planned — see ROADMAP.md)
make android Capacitor + gradle (planned — see ROADMAP.md)
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.
For testing the production-shaped surface — Caddy in front of the
gateway, statically served UI bundle, real https://*.galaxy.lan
hostnames — use the long-lived dev environment at
../tools/dev-deploy/. It is
redeployed by Gitea Actions on every merge into development.
Topic docs
Topic docs live under ui/docs/ (testing tiers, WASM toolchain,
navigation shell, renderer internals, sync protocol, auth flow, and
so on).
Cross-references
PLAN.md— staged implementation plan (historical record of completed work).ROADMAP.md— planned desktop / mobile / multi-turn projection features.PLAN-finalize.md— PWA, accessibility, localisation, error UX finalization work.../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 inui/docs/testing.md.