# WASM Toolchain

The Galaxy UI client compiles the Go module `ui/core` (canonical
bytes, signature verification, keypair helpers) to WebAssembly via
**TinyGo**. The compiled artefact `core.wasm` and its companion
runtime shim `wasm_exec.js` ship under `ui/frontend/static/`.

Real Ed25519 signing happens outside WASM in a platform-specific
keystore — see [`storage.md`](storage.md) for the web implementation
(WebCrypto non-extractable keys + IndexedDB).

## Why TinyGo

Two viable Go-to-WASM toolchains exist:

| Toolchain     | Bundle size                        | Notes                                      |
|---------------|------------------------------------|--------------------------------------------|
| **TinyGo**    | ~903 KB (under 1 MB target)        | LLVM-based, no full GC, fast cold-start    |
| Standard Go   | ~2 MB (`GOOS=js GOARCH=wasm`)      | Drops in without extra tooling             |

`ui/core` was written under the TinyGo invariants documented in
`ui/core/README.md` (no `crypto/x509`, no `encoding/pem`, no
goroutines or `sync` primitives in production files), so the TinyGo
build is a drop-in compile.

The standard-Go fallback stays available in case TinyGo lags behind a
future Go release we depend on. To switch the build, swap the
`tinygo build` invocation in `ui/Makefile` for
`GOOS=js GOARCH=wasm go build -o frontend/static/core.wasm ./wasm`,
and copy the matching shim from
`$(go env GOROOT)/lib/wasm/wasm_exec.js`.

## Prerequisites

- TinyGo ≥ 0.41 (`brew install tinygo`).
- Go 1.26+ (TinyGo's host compiler).
- `buf` 1.67+ on PATH for TS protobuf generation
  (`brew install bufbuild/buf/buf`).
- pnpm + Node 22+ for the JS runtime.

## Build commands

```bash
make -C ui wasm        # produces frontend/static/{core.wasm,wasm_exec.js}
make -C ui ts-protos   # regenerates frontend/src/proto/* from gateway/proto
```

`make wasm` runs `tinygo build -target=wasm` and copies the matching
TinyGo shim into the static asset directory. The shim **must** be
the TinyGo one — the standard Go shim is ABI-incompatible. The
Makefile resolves the shim path via `tinygo env TINYGOROOT`.

## Loading recipes

### Browser

SvelteKit serves `static/` at the application root, so the WASM
adapter at `ui/frontend/src/platform/core/wasm.ts` does the
following on first call:

1. Inject `<script src="/wasm_exec.js">` if `globalThis.Go` is
   undefined.
2. `fetch('/core.wasm')` and instantiate via
   `WebAssembly.instantiate`.
3. Spawn the Go runtime via `go.run(instance)`.
4. Read `globalThis.galaxyCore` and wrap it as the typed `Core`
   interface.

The module is cached for the life of the page; subsequent
`loadCore()` calls reuse the same `Core` instance.

### Vitest under JSDOM

JSDOM does not implement `fetch` against `file://` URLs, so the
test loader at `ui/frontend/tests/setup-wasm.ts` reads both files
from disk and evaluates `wasm_exec.js` via Node's
`vm.runInThisContext`. The result is the same `Core` shape; the
JS-side bridging is identical.

## Field-name convention

The TS `Core` interface uses **camelCase** keys (matching the rest
of the SvelteKit / TS-side conventions). The Go bridge in
`ui/wasm/main.go` reads the same camelCase keys (`protocolVersion`,
`deviceSessionId`, etc.) directly from the JS objects via
`syscall/js`. There is no snake-case translation layer.

## Byte marshalling

The bridge avoids `js.CopyBytesToGo` and instead does an explicit
per-element copy via `value.Index(i).Int()`. TinyGo 0.41's
`js.CopyBytesToGo` panics on `Uint8Array` instances that originate
from Node's `Buffer` (which surfaces in Vitest's JSDOM environment
when fixtures are read with `crypto.createHash`). The per-element
loop is slower in theory but irrelevant in practice: the canonical
envelope payloads stay below a few hundred bytes.

## Reproducibility

TinyGo builds are not bit-for-bit deterministic (the binary embeds
build-machine identifiers). Treat the committed `core.wasm` as a
snapshot rebuilt by `make wasm` whenever `ui/core/` or
`ui/wasm/main.go` changes. CI rebuilds the artefact from source for
its own asserts; the committed copy keeps Vitest from depending on
TinyGo being installed in every environment.

## Bundle size

| Build        | Date       | Size  |
|--------------|------------|-------|
| Initial land | 2026-05-07 | 903 KB |

If the artefact ever crosses the 1 MB target, profile via
`tinygo build -size full` and trim before committing.
