The new-project flow
1. Describe what you’re building
On the Modaal home screen, type your idea into the composer — the placeholder says “Describe the app you want to build…”. You can attach files too: a PRD, a screenshot, a design doc, a Figma link. Then press Start new project. Nothing is created yet. The button opens a two-step wizard, and cancelling at any point writes nothing to disk — no project record, no folder, no git repository.2. Pick a template

Step 1 — the Multiplatform lane first, then the iOS lane: iPhone and iPad apps, then the Apple Watch ones.
- Multiplatform (iOS + Android) — the two Duet cards, which build a native iPhone app and a native Android app from one shared core.
- iOS — the single-platform templates: the iPhone/iPad apps first, then the Apple Watch ones.
- The job it does — iPhone and Android together, Quick prototype, 2D game / Interactive app — with the outcome it targets on the line below (One codebase, both stores, Test an idea fast).
- What you get — a plain-language description of what is scaffolded.
- The architecture it scaffolds, in the grey line at the bottom (Duet architecture (Kotlin Multiplatform), MVVM architecture, RIBs architecture, RIBs + SpriteKit architecture, MV architecture).
3. Confirm the name and identifiers
Step 2 asks for the handful of things Modaal needs before it can generate a project:
Step 2 — name, identifiers, devices, and the collapsed Orientations section.
4. Create
Pressing Create runs three steps:The project is created with its final identity
The template is scaffolded
xcodegen.yml, build configurations, an app icon, and a passing test suite. Any files you attached are copied into the project.Your original prompt is sent as the first message
5. The first turn: PRD and first feature spec
The wizard has already answered the structural questions, so the agent’s first turn starts at product scope. It:- Reads your description (and any attachments — images, PDFs, Figma links).
- Writes a structured
PRD.mdat the project root. - Plans a workable first iteration and writes it to
specs/001-<feature>/spec.md.
.modaal/project.json, so that turn neither asks which architecture to use nor re-scaffolds. It may ask a product question if your scope is ambiguous.
When the plan looks right, turn Plan off and say “let’s build this”. See Modes and prompts for how the Plan toggle works from there on.
The templates at a glance
.xcconfig-based build configurations (Debug / AdHoc / AppStore), an Icon Composer app icon, a localization catalog (Localizable.xcstrings + SwiftGen), a theming module with semantic design tokens, and a test suite that passes from the first build (Swift Testing in Quick prototype, XCTest in the RIBs templates, scenario tests and recorded fixtures in the Duet ones).
What each template scaffolds
iPhone and Android together — Duet, Kotlin Multiplatform flavor
iPhone and Android together — Duet, Kotlin Multiplatform flavor
Given/When/Then scenario, then tools/duet record to compile it into fixtures, then the reducer until the scenario passes, then a SwiftUI screen and a Compose screen that read state and send actions. tools/duet verify replays every fixture on both platforms and compares byte-for-byte; the same gate runs in CI on every push.The iPhone app consumes the same compiled Kotlin logic as the Android app, so there is no second implementation of a reducer to keep in step.See Duet for the full picture and Handling platform-specific UI for where the shared/native line falls.iPhone now, Android later — Duet, Swift flavor
iPhone now, Android later — Duet, Swift flavor
Quick prototype — MVVM
Quick prototype — MVVM
Features/, each with a SwiftUI View and an @Observable ViewModel. Navigation is a Route enum driving a NavigationStack. Services are injected through @Environment. Tests target the ViewModels.Production app — RIBs (Legacy)
Production app — RIBs (Legacy)
Builder (constructs it), Router (attaches and detaches children), Interactor (business logic), View (SwiftUI or UIKit). Children attach to parents, so the router hierarchy is where the app’s navigation state lives, and you can read the whole app off it. Mocks are generated automatically by Sourcery from /// sourcery: CreateMock annotations, so every module is unit-testable in isolation.A Splash RIB and a Main RIB come pre-wired under Root, so the app runs the moment it’s created.2D game / Interactive app — RIBs + SpriteKit (Legacy)
2D game / Interactive app — RIBs + SpriteKit (Legacy)
SKScene or an SKNode instead of a SwiftUI view, and the whole app has exactly one view controller — the scene navigator that owns the SKView.SKNode subclass you addChild into the host scene. A node reads no ambient state (self.scene, UIScreen, the view) and lays out against the metrics its host hands it, so the same node can be attached into a different scene without edits. Animation is SKAction choreography rather than a per-frame game loop, and layoutScene() runs on presentation, on rotation and on every safe-area change.The SpriteKit machinery — BaseScene, the scene navigator, the routing protocols, the sound pool, the atlas loader, the button control — is the open-source modaal-foundation-spritekit package, pinned by URL in the app module’s Package.swift. It is a versioned dependency: you import its products, and it is upgraded by moving the pin.The scaffolded app runs immediately: splash → menu → a tap-the-target sample game with a score and a sound effect, plus a settings screen that persists sound volume. The sample game and the four placeholder sprites in Art/Game/ are there to be replaced — only the sample game in RIBs/Game uses them.See Building 2D games with SpriteKit for what this looks like in practice.Watch only — MV
Watch only — MV
@Observable services directly through @Environment. At watch-screen scope a ViewModel per screen adds a layer without adding testable logic, so MV omits it. Tests target the services.A watch-only project has no Libraries/ or SharedLibraries/ yet — the watch target holds its own sources, and the SPM structure appears the moment an iOS target is added.Watch + iPhone — RIBs + MV
Watch + iPhone — RIBs + MV
Empty project
Empty project
.modaal/project.json — the template at the project level, and the architecture per target. Both the agent and Modaal’s tooling read it, which is why you never have to restate your architecture in a prompt.Choosing a template
Four questions, in this order. The first is the one that decides the most: platform reach comes before architecture.1. Will this ship on Android, now or later?
Yes, or probably → a Multiplatform card. Both scaffold Duet: each feature’s logic is written once, both apps render it natively, and recorded fixtures gate the two against each other in CI.2. Is this a prototype or a product?
A prototype — idea validation, an investor demo, a personal utility with a fixed scope → Quick prototype. Pure SwiftUI, a View and a ViewModel per screen, no module boilerplate beyond that, and a working app in hours. A product — something that will live long, grow past a dozen screens, or earn money → a Multiplatform card, even when only iPhone is planned. iPhone now, Android later is the production template for an Apple-only product.3. Is the screen drawn rather than laid out?
Sprite artwork placed at coordinates, animated per frame, hit-tested where it is drawn → 2D game / Interactive app. It is the only card that scaffolds a SpriteKit scene host, so it is the answer for a game even though it carries a Legacy chip. The section below covers where that line falls.4. Does the product live on the wrist?
Watch only for a standalone watch app, Watch + iPhone for a companion to a phone app. watchOS targets are MV, and that is not configurable.Quick reference
What the Legacy chip means
Production app and 2D game / Interactive app scaffold RIBs (Router-Interactor-Builder, via CombineRIBs): explicit module boundaries, a navigation tree that lives in the router hierarchy, and protocol contracts between modules. Four things follow from the chip:- Projects already on them are fully supported. Features written today follow the same RIBs patterns, the agent knows them, and nothing about the templates has been withdrawn.
- The chip is about new projects. For a new production app, a Duet card gives you comparable structural support plus a recorded behavior corpus and a route to Android.
- A game is the exception. No Duet card scaffolds a SpriteKit scene host, so 2D game / Interactive app is the card for a game regardless of the chip.
- There is a way across. A CombineRIBs project can be carried to the Duet shape feature by feature, in place — Migrating a CombineRIBs app to Duet walks through the stages, what each one costs, and where you can stop. That work is real, and avoiding it is what the chip is pointing at.
When to pick the 2D game card
Pick it when the screen is drawn rather than laid out — sprite artwork placed at coordinates, animated per frame, hit-tested where it is drawn. Games, arcade and puzzle apps, animated lessons, and kids’ apps where the play is the product. Stay on a conventional card for a conventional app, including an animation-heavy one. Rich SwiftUI animation is not a reason to move to SpriteKit. Building a single playful screen on a scene tree means that screen takes on the scene lifecycle, the layout pass and the atlas pipeline, and its UI is assembled fromSKNode subclasses rather than from your existing SwiftUI views.
Case studies
1. Workout interval timer → Quick prototype
1. Workout interval timer → Quick prototype
TimerViewModel with @Observable holding timer state, a NavigationStack with NavigationLink and .sheet(), @Environment for the persistence service, and tests against the ViewModel with zero navigation mocks.A few hours after describing the idea, Max has a working MVP. If he later adds a WidgetKit widget or a watch companion, the main app’s architecture doesn’t constrain them — those targets run on pure SwiftUI either way.Takeaway. For 3–10 screens with a clear, bounded scope, MVVM is the right call. Don’t “play it safe” with a heavier architecture just in case.2. Mobile banking → iPhone and Android together
2. Mobile banking → iPhone and Android together
- Android in six months. iPhone and Android together is the only card that emits an Android app target. Picking it at creation makes Android a target rather than a migration, and the six-month date stops being a second project plan.
- Navigation. KYC is a multi-step flow with branches (document photo → selfie → verification → waiting → result, with back-navigation). Transfers are another flow behind an auth gate. In Duet those transitions are reducer state, recorded as fixtures — so the flow is pinned byte-for-byte and both apps replay the same recording.
- Regulatory scrutiny. Every flow ends up with a written spec under
parity/feature-specs/and an executable recording of what it does. That corpus is produced by ordinary feature work, not by a separate documentation effort. - Security. Features are separate modules with typed boundaries. The payments module has no access to KYC’s internal state, and the compiler enforces that rather than convention.
- Native SDK work. Biometrics, document-verification SDKs and custom PIN entry live in workers behind ports, with one implementation per platform. The reducer names the effect; the platform supplies the I/O.
3. Local services marketplace → iPhone now, Android later
3. Local services marketplace → iPhone now, Android later
- “Android not now” commonly becomes an urgent requirement 6–8 months after launch. On a Duet card that is a scheduled port; on Quick prototype it is a second project.
- Navigation is a real flow: pick service → pick date → confirm → pay → result, with an auth gate (browse without an account, book with one). Chat is a separate real-time flow.
- Integrations: Stripe, WebSockets, push, geolocation.
4. Corporate directory → Quick prototype
4. Corporate directory → Quick prototype
5. The prototype that took off
5. The prototype that took off
- A — Evolve the MVVM codebase. Keep adding features, and for complex flows (paywall, challenge onboarding) add lightweight flow coordinators:
@Observablestate machines that own navigation for a single flow. Not full MVVM+C — no coordinator protocol, no coordinator tree, no parent-child lifecycle. Just one concrete object per complex flow. - B — Restart on a Multiplatform card. MVVM has no migration path in Modaal, so this is a new project rather than a conversion: the agent rebuilds the app on a Duet card, and the business logic — models, services, CloudKit code — carries across as-is, because it lives in SPM modules rather than the architectural layer. The screens are written again.
Changing your mind later
Not everything the wizard asks is permanent. Here’s what is:Modaal currently supports Duet in two flavors for multiplatform projects, MVVM, RIBs and RIBs + SpriteKit for iOS, and MV for watchOS. Duet is pre-release and its cards carry a Beta chip; Android is in open beta, with general availability in September 2026.