Design docs for work that is proposed, in flight, or partly landed. These are the historical record —
the why, the alternatives that were rejected, and the decisions still open. The user-facing reference
lives in docs/; when a plan and the docs disagree, the docs describe what the code
does today and the plan describes how it got there.
A plan is deleted once it is fully implemented and its content lives in docs/ (e.g. the Skia backend
plan, removed 2026-07-19 — see Skia backend).
| Plan | Status | What's left |
|---|---|---|
| Dependency injection | Phase 1 shipped | ISwiftDispatcher for the Skia hosts; verify the Windows head; SDN1003 false positives. Docs: Hosting & DI |
| Page & view lifecycle | Partially shipped | The big one: native visibility emitters — OnAppearing isn't wired to real platform visibility yet. Then IAppLifecycle, .OnChange, OnAppearAsync(ct) |
| Controls: missing framework features | Partially shipped | Wave A done; F7 collections, F8 drawing canvas, F10 services, F11 geometry |
| Controls library | Partially shipped | VirtualizedGrid, ~8 cell types, Compose/WinUI camera renderers; camera not device-verified |
| Safe area insets | Implemented, unverified | Device/simulator run (notched iOS sim + Android 15 emulator); RTL reconciliation; a sample that uses it; decide on the SafeAreaRegions name collision with MAUI. Docs: Safe area |
| Skia MAUI host | Resolved — iOS + Android verified, incl. touch scroll / slider / soft keyboard | Caret placement & selection; keyboard avoidance; pan inertia; automated coverage of the MAUI adapter; the Windows head. The "repaint defect" was a sample bug (a rebuilt stateful child), not a host bug. Docs: Skia |
| Accessibility & screen readers | Draft — nothing built | Everything. Phase 1 (Core modifiers + the $a11y settings channel) is standalone; Phases 3–4 (the Skia accessibility tree + its iOS/Android host adapters) are where the real gap is — Skia is a single unlabelled rectangle to VoiceOver/TalkBack today |
| Game surface & real-time rendering | Draft — nothing built | Everything. Phases 1–3 (a Canvas node, a DisplayList recorder, .OnFrame(dt) vsync, raw input, paths/blend/atlases) supersede F8 and stand on their own for charts/signature pads; Phase 4 (SwiftDotNet.Game) is a separate bet to decide at the Phase-2 checkpoint. Key finding: IBridge.Render takes JSON on every backend, so the surface must bypass it entirely |
| Navigation service | ⏸ Paused | Everything. Would be the first consumer of ViewScope (built, no caller) |
| View construction seam | Draft | Decision 1 — adopt the function form (Text() vs new Text())? The [Inject] generator it once owned already shipped |
| Windows / Scenes (multi-window) | Draft — nothing built | Step 0 is de-singletoning SwiftApp; then the Swift shim host-handle refactor |
| MSBuild SDK / custom TFMs | Draft — nothing built | Everything. Prototype-verified: a wrapper SDK is cheap; custom TFMs work but are viral (NU1202 for stock-SDK consumers) |
| Native-view access | Draft — nothing built | Everything (.Tag + per-backend Customize registries) |
| Rider plugin | Phases 1–4 built; verified inside Rider headlessly | Press Run once by hand — swiftdotnet-doctor verifies discovery/gate/devices/planning inside Rider and both mobile heads deploy from its planned commands, but the final getStateAsync → Rider-runner hop is unexercised; confirm IRiderDebuggable satisfies Rider's debug runner; move the iOS delta-applier reference into the SDK. The Phase-4 spike solved iOS hot reload — the cause was dotnet watch's startup hook, not the 127.0.0.1:10000 socket. Docs: Rider plugin, Hot reload |
| Live Activities & rich notifications | Implemented — pure C# tested, drivers unrun | Device/emulator runs, and the five Phase-0 facts they would settle (widget memory budget; whether a custom Layout survives archiving; whether LiveActivityIntent.perform() really runs in-app; real RemoteViews parcel limits; API-36 promotion). Key findings once built: View.BuildNode being internal did not block a satellite vocabulary (LiveView emits Node directly), the Kotlin interpreter proved unnecessary (a RemoteViews is a recipe, and the provider is our process), the SwiftDotNetWire extraction was dropped (the two decoders share nothing), and LiveTimer — a self-ticking clock costing zero updates — turned out to be the most valuable node in the vocabulary. Docs: Live surfaces |
| Widgets & the surface channel | Implemented — pure C# tested, drivers unrun | Widget configuration (WidgetConfigurationIntent generated from C#), a dotnet new template for the Xcode extension target, and a device run. The Apple inversion held up in the build: TimelineAsync cannot run in the extension, so the app pre-renders every entry × placed family into an App Group and SDNTimelineProvider is a dumb reader — which means a widget only shows what the app already computed, and never refreshes itself. Docs: Live surfaces |
| Wayland host | Draft — not committed to build | Everything; explicitly not scheduled |
| CarPlay & Android Auto | Draft — nothing built | Everything. Phase 0 is a two-platform spike that answers whether the androidx.car.app binding exists and which CarPlay templates update in place. Key findings: both platforms are template systems (the existing View DSL can't lower into them), both bridges are pure C# (no Swift shim — a first for an Apple target), the car vocabulary must live in Core because View.BuildNode is internal, and Tier B (the navigation drawing surface) is nearly free reuse of the Skia engine. Gated by Apple/Google category entitlements we don't control |
| MAUI interop | Implemented — engine seam CI-tested; MAUI half builds, never run | Drive the sample's MAUI tab by hand on the iOS simulator and Android emulator (scroll tracking, clipping, the Sheet suppression rule, IME hand-off); a Map platform-view renderer; an IPlatformViewHost for the non-MAUI Skia hosts so WebView punches through there too; the WinUI half has never compiled. Key findings: inside a MAUI host no IMauiContext/embedding is needed (the host is a MAUI ContentView), MAUI's UseMauiEmbedding is internal in 10.0.80 (the public path is UseMauiEmbeddedApp + ToPlatformEmbedded, and naming the type is CS0433), the factory can't ride the JSON wire so identity is the node id, transforms are paint-time and therefore a no-op on platform views, and every Core reference must be TFM-pinned or a -ios build resolves the SwiftUI slice. Docs: MAUI Interop |
Four plans defer their last phase to the same unstarted milestone — keyed identity for child View
instances across renders, so an inline Body child is a stable object rather than a fresh one each pass:
- DI — container-created child views, scoped-per-view lifetimes
- Page lifecycle — lifecycle for inline children
- View construction seam — Tier 1 positional retention (this plan is that milestone, approached from the construction side)
- Animations — enter/leave transitions, keyed
ForEach
Nothing has started on it. It is the single highest-leverage piece of unbuilt framework work.
SwiftApp keeps _bridge, _lastTree and _uiContext in statics, so exactly one view tree can be live
per process. Windows / Scenes names this as its Step 0. It is small and well-scoped, but
it has no concrete bug driving it — the Skia MAUI host defect was once
attributed to a second host view rebinding those statics, and instrumentation on a simulator disproved that
(Run was called exactly once; the real cause was a rebuilt stateful child in the sample). The motivation
is multi-window, and multi-window alone.