# Architecture

SwiftBrowser runs SwiftUI apps in the browser so iOS development does not
require a Mac for day-to-day iteration. The invariant everything else
protects: **an app's source imports `SwiftUI` and compiles unchanged for both
iOS and the browser.**

```
Examples/Counter/Sources/*.swift      the app: plain SwiftUI, no SwiftBrowser imports
        │                                      │
        │ swift build --swift-sdk wasm          │ xcrun swiftc -typecheck (macOS CI)
        ▼                                      ▼
Sources/SwiftUI (shim module)            Apple's real SwiftUI
  view tree → element tree → ops
        │
        ▼  docs/ops-protocol.md
Web/ (TypeScript renderer)   or   headless JSON Lines (wasmtime, CI snapshots)
```

## Pieces

- `Sources/SwiftUI`: a SwiftPM library target literally named `SwiftUI`. It
  implements an API-compatible subset of SwiftUI (see the compatibility matrix
  in the README), state storage, and a reconciler that diffs element trees
  into ops. It has no dependency on Foundation or JavaScriptKit, so it also
  builds natively on Linux for fast unit tests.
- `Examples/Counter`: the first app. Its sources are compiled two ways: into a
  Wasm executable against the shim, and type-checked against Apple's SwiftUI
  on a macOS runner so the shim can never drift from the real API.
- `Web/`: a Vite + TypeScript project. It instantiates the Wasm module with a
  WASI shim, applies ops to the DOM inside an iPhone-shaped frame styled with
  iOS design tokens, and forwards taps back to Swift.
- `scripts/`: `build-wasm.sh` builds the example for Wasm and copies it into
  `Web/public/`; `snapshot-test.sh` runs the example headless under wasmtime
  and diffs against `Examples/Counter/__snapshots__/`.

## Why this shape

- Emitting ops instead of touching the DOM keeps the Swift core testable
  without a browser and lets the renderer be swapped (a canvas renderer with a
  faithful SwiftUI layout pass is the Phase 2 option).
- The package is only ever built for Wasm and Linux. Building it on macOS
  would collide with the system `SwiftUI` module, which is intentional: on
  Apple platforms the app uses the real thing.

## Phase 2 notes

- **Keyed children.** `ForEach` gives its children identities. Both the
  mounted tree and the reconciler match keyed children by identity, so a row
  keeps its `@State` and its element id across inserts, removes and
  reorders, and a reorder becomes a single `insert` (move) op.
- **Navigation.** `NavigationStack` keeps the pushed screens in a `@State`
  array inside its own view struct, so it survives re-renders like any
  state. The runtime wraps the root and each pushed view in a `navscreen`
  element, lifts `navigationTitle` into the screen's props, supplies the
  push handler for `NavigationLink` and the pop handler for the screen, and
  puts a matching `dismiss` into the environment.
- **Environment.** Values flow down during reconciliation. `@Environment`
  properties are found by reflection, like `@State`, and resolved before the
  view's body runs. `.environment(_:_:)` and style modifiers write into the
  same context.
- **Observation.** A render pass runs inside `withObservationTracking`; a
  change to any observed property marks the tree dirty, and the next event
  (or the end of the current one) re-renders. Mutations outside events, such
  as from timers or tasks, are not yet driven to the screen.
- **Hot reload.** The runtime can snapshot primitive `@State` values keyed
  by each view's path (position and type from the root) and restore them
  from the `SB_STATE` environment variable at the next boot. Values whose
  path or type no longer matches are dropped silently.

## Phase 3 notes

- **Layout lives in the renderer.** The Swift side still emits only the
  element tree. The renderer's layout engine (`Web/src/layout`) implements
  SwiftUI's propose-and-report protocol per element kind, measures text with
  the real font, and positions every element absolutely. Keeping layout in
  TypeScript avoids a synchronous Swift-to-JavaScript measurement bridge,
  keeps the module runnable under wasmtime with no custom imports, and lets
  the engine be unit-tested in Node with a deterministic text measurer.
- **Skipping unchanged views.** A node re-runs its body when it is dirty
  (its own state or an observed value changed). When its parent re-runs it
  receives a new view value; if that value and the environment it receives
  are the same as last time, it is skipped with its subtree, like SwiftUI
  does for views whose stored properties are equal (docs/ops-protocol.md,
  "Phase 16"). Which of the runtime's protocols a view conforms to is
  worked out once per type (`_ViewTraits`), not cast for on every pass
  ("Phase 17").
- **Animation.** `withAnimation` records a transaction on the main actor;
  the render pass it causes stamps its `commit` with the animation and the
  renderer animates every frame and style change in that pass.
  `.animation(_:value:)` is a styled wrapper whose mounted node remembers the
  last value and bumps an `animationToken` prop when it changes; only that
  subtree animates. Transitions are a style field read when the element is
  inserted or removed inside an animated commit. A pass that moves an
  element whose frame is still tweening (a custom `Layout` or a
  `GeometryReader` answering the animated pass a frame later) retargets the
  tween from where the element is drawn to its new frame, in the time the
  tween had left; it never leaves the old tween drawing a stale frame.
- **Sheets.** `.sheet` mounts the content as a second child of the
  presenting node while presented, so its state survives, but emits it as a
  root-level `sheet` element after the app's tree. Its handler flips the
  binding; the content's environment carries a matching `dismiss`.
- **Dynamic Type.** Fonts travel as text-style names and the renderer
  resolves them against Apple's Dynamic Type table for the current size,
  which arrives through the environment event like the color scheme.

## Phase 4 notes

- **Executor.** Swift concurrency on Wasm has no event loop of its own. The
  shim installs a cooperative executor through the `ExecutorFactory` SPI
  (the same route JavaScriptKit takes on Swift 6.4): jobs queue until the
  host calls `sb_run_jobs`, and sleeps become timers against a clock the host
  sets. The browser passes real time; headless mode jumps a virtual clock
  from timer to timer, so timer-driven apps snapshot deterministically.
  Native test processes never install it, because the test runner drives the
  main actor itself.
- **Stable state storage.** A long-lived task captures the view value it was
  started from. State boxes therefore share one persistent storage object
  across re-renders instead of copying values, so a closure captured before
  a re-render still writes to the live value.
- **Tabs and pickers.** The runtime flattens the content into options,
  reading `tag`, `tabItem` and `ForEach` ids through the wrapper views, and
  emits one `tab` element per option (all mounted, one selected) or the
  option labels on a `picker`.
- **Value-based navigation.** `navigationDestination(for:)` registers a
  builder on the enclosing stack's node while the root screen reconciles;
  path screens are placeholders resolved right after, so a value appended to
  the path binding becomes a screen in the same pass.
- **GeometryReader.** The renderer reports the laid-out size through a
  `geometry` event only when it changes; the node stores it and the content
  re-renders with the real size, so the loop converges in one extra pass.

## Foundation

The shim imports Foundation and apps link the wasm SDK's swift-corelibs
Foundation, with ICU's locale data trimmed to the app's languages by the CLI
(see `packages/cli/src/icu.ts` and docs/ops-protocol.md, "Phase 11"). What
corelibs lacks on WASI lives in `Sources/FoundationBridge`: `Timer` and
`RunLoop`, a `UserDefaults` the browser persists, a `Bundle` over the staged
resources, `URLSession` and `AsyncImage` over the page's `fetch`, and the
viewer's `Locale`. An app file says `import Foundation` and nothing else, so
the same file compiles against Apple's SDKs. Earlier phases had a lean mode on
`FoundationEssentials` plus a small formatting layer; Phase 14 removed it
(README, "Phase 14"). Phase 15 trimmed further (docs/ops-protocol.md,
"Phase 15", with a breakdown of where the bytes go and why dead-code
elimination keeps them); a small app is 17.4 MB, 4.5 MB with brotli. The
sizes below are from the lean era and remain a fair picture of what each
piece costs before trimming.

| Build (release, `-Osize`, stripped) | Raw | gzip |
|---|---|---|
| Settings example (no Foundation) | 9.6 MB | 2.6 MB |
| FoundationEssentials probe | 12.7 MB | 3.7 MB |
| Full Foundation probe | 53.8 MB | 19.1 MB |

## SwiftData

`Sources/SwiftData` is SwiftData over the page's IndexedDB (docs/ops-protocol.md,
"Phase 18"). The store is in Swift memory for the run, handed in at launch
(`SB_SWIFTDATA`) and written back save by save (`swiftdata` ops), because
SwiftData's API is synchronous and IndexedDB's is not: fetching from memory
keeps `context.fetch` and `@Query` synchronous, as on iOS. `@Model` and
`#Predicate` are compiler plugins (`Sources/SwiftDataMacros`, swift-syntax),
so only apps that import SwiftData pay for them; the CLI links the module
when a source imports it.

## Files

An app's home directory (`/data`, `HOME`) is an in-memory WASI directory the
page fills from IndexedDB before the module starts and writes back after any
event or executor run that wrote to a file (docs/ops-protocol.md, "Phase 19").
Nothing in the protocol changes: the page notices writes by watching the WASI
calls that can change a file, then diffs the tree against what it last saved.

## Bitmaps

`UIImage`, `CGImage` and Core Image's `CIImage` hold recipes, never pixels
(docs/ops-protocol.md, "Bitmaps and Core Image"): an image file's bytes (or a
generator's pixel buffer, such as a QR code's modules) plus the filter steps
to run. Swift reads sizes from file headers, so `UIImage(data:)` stays
synchronous as on iOS; the page decodes the file with the browser and runs
the steps in TypeScript (`Web/src/imageOps.ts`) at the size the image is
shown. No image codec is compiled into the module. `UIImage` and `CGImage`
are in the shim because SwiftUI re-exports them on iOS; `Sources/CoreImage`
is linked only into apps that import it.
