The map view now selects a DARK_THEME or LIGHT_THEME palette from the resolved app theme and threads it through every primitive builder, so the canvas, planets, ship groups, cargo routes, battle/bombing markers, fog, reach + selection rings, pending-Send tracks, and the pick overlay all switch with the rest of the chrome. A theme flip remounts the renderer preserving the camera — Pixi bakes the background at init and every primitive bakes its colour at build, so a live re-tint is not possible on the same instance. This also fixes the reported bug: the gear-popover trigger and the loading overlay hardcoded a dark navy background, so in light theme the gear was invisible (dark icon on dark chip) until hover flipped it to a white chip. Both now use the --color-surface-overlay token and read correctly in both themes. The light palette mirrors the dark one role-for-role, darkened / saturated for contrast on a light background while keeping the incoming, battle, and bombing accents vivid. The values are a first pass meant to be refined during the F8 manual-QA loop. Removes the now-dead "Phase 35" references from the code and lifts the map-recoloring prohibition from the design-system / renderer docs; the battle scene stays a fixed-palette data-viz surface. Co-Authored-By: Claude Opus 4.7 <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.