Sky.Spa — production web (exploration + measured evidence)

Status: exploration (v2). Sky.Spa v1 ships desktop/mobile-embed weight (Go→wasm, ~2.4 MB gzip for the Todos app). This document is the evidence-based plan for a web-viable bundle. Every size here is measured on this machine.

The goal

A public-web-viable client bundle. Reference points: Elm ~30 KB gzip, a React app's runtime ~40–130 KB gzip. Sky.Spa v1's Todos client is ~2.4 MB gzip — fine for logged-in/internal tools + installable PWAs (load-once, cached), too heavy for cold public web.

The blocker (confirmed against the code)

The wasm client core is reflection-native, in three places, all compiled into the client (none //go:build !js):

TinyGo — the only lever that shrinks wasm ~10–20× — implements neither reflect.Value.Call nor reflect.MakeFunc. So it cannot compile this core. The half-built escape hatch does not help: perMsgTypedDispatch (msg_dispatch.go) is Stage-5 scaffolding only — it always falls through to reflect (LookupMsgUpdate is empty in every real binary; codegen emits no RegisterMsgUpdate; the Stage-6 "thread the ADT name from the call site" step never landed).

Measured evidence (this machine, 2026-08-22)

The Phase-1 spike (docs/skyspa/spike/main.go) is a reflection-free Go→wasm TEA+DOM counter — a faithful stand-in for what a de-reflected Sky.Spa client would be. Compiled both ways:

Toolchainrawgzip
standard Go (GOOS=js)1,996,836 B (1.90 MB)592,397 B (579 KB)
TinyGo 0.41.1 (-target wasm)195,970 B (191 KB)66,999 B (65 KB)

~9× smaller, 65 KB gzip — web-viable. And the current v1 real-app figure for contrast: the Todos client (reflect-heavy, standard Go) is ~2.4 MB gzip.

A trivial counter under-measures, so a todos-SCALE reflection-free surrogate was also built (scratchpad/tinygo-complex — a {ui,data} model, switch-based typed dispatch over 12 Msg branches, a real list+form+filter+router view with per-item rows, a hand-written typed JSON codec, syscall/js fetch):

todos-scale, reflection-freerawgzip
standard Go→wasm2,077,533 B614,485 B (600 KB)
TinyGo 0.41.1301,206 B105,020 B (103 KB)

103 KB gzip for a realistic app — decisively web-viable. Against the current reflect-heavy Todos client (~2.4 MB gzip), Path A is a ~23× reduction. This is the number that matters: the de-reflected design, at real app complexity, lands in production-web territory.

So the two facts that decide the plan: (1) TinyGo turns a reflection-free client into a web-viable bundle; (2) the current client is reflection-heavy, so TinyGo is blocked until it is de-reflected.

The three paths

Path A — de-reflect the client core, compile with TinyGo (recommended)

Make the client core reflection-free, then TinyGo it. Work:

  1. Dispatch — finish the perMsgTypedDispatch Stage 6: a typed dispatch table keyed on the Msg ADT (thread the ADT name from the call site), so update/view are invoked without reflect.Value.Call. Scaffolding exists.
  2. Codec — the client decodes data with Codec.auto (reflection). Replace with the typed/generated codecs the app already declares (the Todos app hand-writes todoCodec etc. in Shared.sky — those are reflection-free already; the reflection is in Codec.auto's auto-derivation). A codegen or a reflection-free Codec.auto for the client target.
  3. ADT shape — a reflection-free ADT representation for the client.
  4. Isolate — do all of this in a separate rt_spa client runtime (or a //go:build js && spa variant), so Sky.Live's server reflect path is untouched and unregressed.
  5. Verify TinyGo covers the rest — TinyGo's stdlib is narrower than Go's (not only reflect). The whole reflection-free client core must be checked against TinyGo's supported packages, not just the spike.

Path B — a Sky→JS backend (long-horizon ideal)

A new codegen target emitting JavaScript from the existing typed IR (like Elm→JS). Smallest bundles (~30 KB), best web perf, and JS's dynamism dissolves the reflect problem entirely.

Path C — ship Go→wasm for web now, mitigated (interim)

Keep standard Go→wasm; mitigate the ~2.4 MB: brotli (smaller than gzip), streaming compilation, lazy-load/split, aggressive caching.

Recommendation

Honest caveats