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.
The home-screen composer. The paperclip attaches source materials; Start new project opens the wizard.
2. Pick a template

Step 1 — six cards, two to a row: the two Duet cards, then the iPhone app cards, then the Apple Watch cards. The empty-project link is below them.
- 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 (Swift), Duet architecture (Kotlin Multiplatform), MVVM architecture, RIBs + SpriteKit architecture, RIBs architecture, MV architecture). The (?) at the end of that line opens this section of the documentation without selecting the card.
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. Android is listed under Devices because the default card, iPhone and Android together, has an Android target.
How the three fields fit together
The first three fields are one chain. Product name and organization identifier are the inputs; the bundle identifier is the output:- Product name is the user-facing field. It is the display name under the icon and the default name of the App Store listing, and it names the project folder Modaal creates. Renaming an app later is a normal change.
- Organization identifier is an internal naming convention and nothing more. The custom is to take a domain you control and write it back to front: a personal site
janedoe.devgivesdev.janedoe, a company siteacme.comgivescom.acme. Following the custom keeps your bundle identifiers unique without any registry, because nobody else owns that domain. Nothing verifies it, though: no check confirms the domain exists or that you own it, the string is never shown to users, and it is not registered with Apple or Google on its own. Its only consequence is the prefix it contributes to the bundle identifier. If you have no domain, any reverse-DNS-shaped string other thancom.exampleworks, such asdev.yourname. - Bundle identifier is the field with external consequences. iOS uses it to tell apps apart on a device, and App Store Connect uses it to tie every uploaded build to one app record, so it must be unique across the App Store. Wherever the app is registered — the Apple Developer portal, App Store Connect, TestFlight, Firebase — it is the bundle identifier that is registered, never the organization identifier or the product name.
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. The Duet tutorials write by hand what this card scaffolds, one feature at a time, so you can read an emitted project and know what each file is for.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 (existing projects)
Production app — RIBs (existing projects)
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
2D game / Interactive app — RIBs + SpriteKit
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 → one of the two Duet cards in the first row of the wizard. On both, each feature’s logic is written once, both apps render it natively, and recorded fixtures gate the two against each other in CI. Building both platforms together is part of Pro (plans on modaal.dev).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 Duet 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. 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.Which template for which situation?
Why Production app is no longer offered
Production app scaffolds RIBs (Router-Interactor-Builder, via CombineRIBs): explicit module boundaries, a navigation tree that lives in the router hierarchy, and protocol contracts between modules. The wizard no longer offers it, because the two Duet cards cover every app it scaffolds: a Duet card gives a new production app comparable structural support, plus a recorded behavior corpus and a route to Android. Three things follow:- Projects already on it are fully supported. They keep building, features written today follow the same RIBs patterns, and the agent knows them.
- A game is the exception. No Duet card scaffolds a SpriteKit scene host, so 2D game / Interactive app, which is RIBs with a SpriteKit view layer, is still offered and is the card for a game.
- 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. Starting a new project on a Duet card means that migration is never needed.
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.
Picking a template for a real app
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 Duet 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:PRD.md from your description. If you would rather bring your own, the app requirements document template sets out what to put in each section.
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; Android support is available to every account.