-
Notifications
You must be signed in to change notification settings - Fork 0
Architecture Overview
James Daley edited this page Apr 25, 2026
·
3 revisions
Forsetti is organized around strict package boundaries. The runtime core has no dependency on UI frameworks, StoreKit, or host app code. Platform and host layers adapt the core to Apple services and presentation.
flowchart TB
subgraph Consumer["Consumer Application"]
AppTarget["App target"]
AppModules["App-owned modules"]
AppServices["Host-owned services"]
end
subgraph Forsetti["Forsetti Package"]
HostTemplate["ForsettiHostTemplate"]
Platform["ForsettiPlatform"]
Examples["ForsettiModulesExample"]
Core["ForsettiCore"]
end
AppTarget --> HostTemplate
AppTarget --> Platform
AppTarget --> Core
AppModules --> Core
AppServices --> Core
HostTemplate --> Platform
HostTemplate --> Core
Platform --> Core
Examples --> Core
| Target | Responsibility | Must Not Do |
|---|---|---|
ForsettiCore |
Define contracts, module models, lifecycle orchestration, events, logging, compatibility, activation, and UI contribution state. | Import SwiftUI, StoreKit, or host app code. |
ForsettiPlatform |
Provide Apple platform adapters such as StoreKit entitlements and default service implementations. | Own app-specific product rules or host-specific UI. |
ForsettiHostTemplate |
Provide reusable SwiftUI host control surfaces, controller state, overlay routing, and view injection registry. | Become the application domain layer. |
ForsettiModulesExample |
Demonstrate modules and manifests for tests and reference. | Become a production dependency by accident. |
| Consumer app | Own concrete app modules, product configuration, services, and end-user presentation choices. | Patch Forsetti internals to solve app-specific behavior. |
flowchart LR
Core["ForsettiCore<br/>lowest layer"]
Platform["ForsettiPlatform"]
Host["ForsettiHostTemplate"]
Examples["ForsettiModulesExample"]
App["Consumer App"]
Platform --> Core
Host --> Core
Host --> Platform
Examples --> Core
App --> Core
App --> Platform
App --> Host
The rule is directional: higher layers may depend on lower layers, but lower layers cannot know about higher layers. Required behavior crosses boundaries through protocols, manifests, service injection, or events.
| Component | Layer | Role |
|---|---|---|
ForsettiRuntime |
Core | Composes services, policy, entitlement provider, registry, loader, module manager, event bus, logger, router, and UI surface manager. |
ManifestLoader |
Core | Loads bundled JSON manifests from ForsettiManifests and validates required fields and duplicate IDs. |
CompatibilityChecker |
Core | Validates schema version, platform, Forsetti version range, and requested capabilities. |
ModuleRegistry |
Core | Maps manifest entryPoint strings to module factories. |
ModuleManager |
Core | Activates, deactivates, restores, persists, and selects modules. |
UISurfaceManager |
Core | Merges active UI module contributions into host-readable state. |
ForsettiContext |
Core | Gives modules access to services, eventing, logging, and routing. |
ForsettiHostController |
Host template | Converts runtime state into UI-ready module lists and user operations. |
ForsettiEntitlementProviderFactory |
Platform | Creates StoreKit or debug/static entitlement providers. |
flowchart TD
Control["Control plane<br/>manifests, compatibility, capability policy, entitlements"] --> Activate["Activation decision"]
Data["Data plane<br/>services, events, logging, routing"] --> Running["Running module"]
Activate --> Running
Running --> UI["UI surface contributions"]
UI --> Host["Host presentation"]
The control plane decides whether a module may run. The data plane is what an already-running module can use safely.
| Constraint | Reason |
|---|---|
| Modules declare capabilities before activation. | Permission-like behavior must be reviewable. |
Paid modules use entitlement checks before start(context:). |
Lock state is runtime policy, not just UI decoration. |
| UI modules expose structured contributions. | Host composition remains deterministic and inspectable. |
| Service dependencies are resolved through protocols. | Modules stay portable and testable. |
| Architecture tests enforce import direction. | Boundary drift is caught in CI instead of by convention. |