Skip to main content
Every Modaal project starts from a template. The template determines what is created on disk before the agent’s first turn: which targets exist, which platforms they run on, and — most consequentially — which architecture your code is written against. This article covers both halves of that decision: the sentence you pick the template with, and how to choose the one that fits what you’re building.

The new-project flow

You start a project by completing one sentence above the composer, describing the app in the composer, and pressing Start new project. There is no second step: nothing else is asked, and the name, the identifiers and the device families are derived from your description.

1. Complete the sentence

The sentence reads I’m building a kind for platform, with an + Apple Watch chip beside it. Each word is a button that opens a menu.
The Modaal home screen under the heading 'Craft native': the sentence 'I'm building a Production app for Android + iOS' with a dashed '+ Apple Watch' chip, above a composer with the placeholder '…that lets people — describe it in your own words', a paperclip, a model selector reading 'Default (recommended)', the Start new project button, and the caption 'You can add features and targets later in chat. How templates work'

The home screen: the sentence above the composer, the composer below it. The paperclip attaches source materials; the selector beside Start new project picks the agent and model.

Word 1 — what you’re building. Three rows:
The kind menu open under the first word of the sentence: Quick prototype (Test an idea fast), Production app (Built to ship, feature by feature, checked), 2D game (Build something playful), and a How templates work link below a divider

Word 1 — the kind. Production app is the opening selection; the check marks the current one.

Word 2 — where it runs. It appears for Production app only — Quick prototype and 2D game each have one template, so the sentence ends after the first word. Two rows:
The platform menu open under the second word of the sentence: iOS (iPhone now, Android later), Android + iOS (One codebase, both stores, checked), a checkbox row Add Apple Watch companion, and a How templates work link below a divider

Word 2 — the platform, with the Apple Watch companion as a checkbox row. Android + iOS is selected here.

Word 3 — the Apple Watch chip. Also Production app only. Unselected it is a dashed + Apple Watch chip beside word 2; clicking it fills it in and it reads Apple Watch, and clicking again clears it. The Add Apple Watch companion checkbox in the platform menu is the same setting — two places to set one value. Switching the kind away from Production app hides the chip and does not send it; switching back restores what it said. Android + iOS is part of Pro (plans on modaal.dev). On a plan without it the row still selects and the sentence reads it, with a Pro chip and the monthly price on the row; Start new project then opens the upgrade screen instead of creating the project, and your draft and your selection are kept. The sentence opens on Production app for iOS the first time, on every plan. After that it opens on the combination you last created a project with. Both menus end with How templates work ↗, which opens this page.

2. Describe the app in the composer

Type your idea into the composer — the placeholder continues the sentence: “…that lets people — describe it in your own words”. The paperclip attaches source materials: a PRD, a screenshot, a design doc, a Figma link. The selector beside the send button picks the agent and model that will do the work. Your description is used three ways: it names the app, it sets the device families (name iPad in it and the project is created for iPhone and iPad), and the agent writes the PRD from it on the first turn. Nothing is created while you type, and nothing is created if you never press the button — no project record, no folder, no git repository.

3. Press Start new project

1

Modaal names the app

The name comes from your description; when nothing can be read from it, the project is created as My App and you can rename it in the Project Settings tab. The organization identifier comes from the one you last created with, or is suggested from your account, and the bundle identifier is derived from both — see The name and identifiers.
2

The project is created

A folder, a project record, and a git repository, all under that name.
3

The template is scaffolded

Targets, SPM modules, xcodegen.yml, build configurations, an app icon, and a passing test suite. Any files you attached are copied into the project.
4

Your original prompt is sent as the first message

The agent receives it with Plan mode on, and continues from there.
You wait on the home screen while that runs. The composer holds your text and goes read-only, the sentence stops responding, the send button reads Setting up…, and the line under the composer names the step — Naming your app…, then Scaffolding, with the name of the template being written. A Cancel button sits beside it until scaffolding starts — after that the project exists, so there is nothing left to cancel. If the create fails, the home screen shows the error and keeps both your draft and your selection.

4. The first turn: PRD and first feature spec

The sentence has already answered the structural questions, so the agent’s first turn starts at product scope. It:
  1. Reads your description (and any attachments — images, PDFs, Figma links).
  2. Writes a structured PRD.md at the project root.
  3. Plans a workable first iteration and writes it to specs/001-<feature>/spec.md.
The architecture and the target layout are already recorded in .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 name and identifiers

Three values are set at creation without asking you. Two of them are one chain — the product name and the organization identifier are the inputs, the bundle identifier is the output:
  • Product name is the user-facing field: the display name under the icon, the default name of the App Store listing, and the name of the project folder Modaal creates. It is taken from your description, and editable afterwards in the Project Settings tab.
  • Organization identifier is an internal naming convention and nothing more. Modaal reuses the one your last project was created with, or suggests one from your account. The custom is to take a domain you control and write it back to front: a personal site janedoe.dev gives dev.janedoe, a company site acme.com gives com.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.
  • Bundle identifier is the field with external consequences, derived as <organization identifier>.<product name without spaces>. 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.
Check the bundle identifier before your first upload. Once a build reaches TestFlight or the App Store, its Bundle ID can never be changed — you’d have to ship a new app. Set it in the Project Settings tab of the sidebar.
Devices and orientations are set from template defaults, plus any device family your description names. Both are editable afterwards in the Project Settings tab, together with the name, the display name, the bundle identifier, the version, the minimum deployment target and the app icon.

The templates at a glance

The sentence has six combinations, and each one names a template: Some things every combination gives you: XcodeGen-generated project files, .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 2D game, scenario tests and recorded fixtures in the two Production app combinations).
Four templates sit outside the sentence and are not creation paths: the CombineRIBs Production app template (ios-ribs), the two Apple Watch templates (watchos, watchos-ios-ribs) and the empty project (empty). Projects already created from them are fully supported, keep building, and keep getting features — see CombineRIBs projects, and What each template scaffolds for the two watch templates. A workspace with no app targets is what an imported repository is; ask the agent to add targets to it in chat.

What each template scaffolds

A native iPhone app and a native Android app in one repository, with each feature’s logic written once as a pure Kotlin reducer that both apps consume.
How you’ll write features: a feature spec, then a 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 combination scaffolds, one feature at a time, so you can read an emitted project and know what each file is for.
One iPhone app, pure SwiftPM — no Kotlin, no Gradle, no Android SDK needed to build or ship it. Each feature is a pair of Swift packages.
How you’ll write features: the same loop as the Kotlin Multiplatform flavor, with the reducer in Swift and one shell instead of two.Every feature is recorded and specced exactly as in the two-platform flavor. That corpus is what makes Android a per-feature, fixture-gated migration later — the Kotlin core and Android shell are added to this same repository, and each feature’s Kotlin twin is verified against fixtures recorded when the feature was first built. The iPhone app keeps building and shipping throughout.
Pure SwiftUI, with the smallest module structure that still keeps the ViewModels unit-testable.
How you’ll write features: one folder per feature under 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.
An explicit module tree, protocol boundaries between every module, and a navigation tree that is explicit in the router hierarchy. The sentence does not create this template — see CombineRIBs projects. This section describes the projects created from it, and the same RIBs structure is what 2D game builds on and what the iPhone target of the watchos-ios-ribs template gets.
How you’ll write features: one RIB per screen or flow — 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.
The CombineRIBs structure with the view layer swapped for a SpriteKit scene tree. Builders, Interactors, Routers, Components and Sourcery mocks are identical; what changes is that a RIB’s presenter is an 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.
How you’ll write features: two scenes (Splash and Menu) and everything deeper as node RIBs — an 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.
Turning the + Apple Watch chip on adds a watchOS app target to whichever Duet combination you picked. It is a target on the project, not a project of its own: the phone app is still what the platform word says, and the watch app is its companion.
What you get on day one: a target in src-ios/App/xcodegen.yml that builds and runs on a watchOS simulator, a bundle identifier of <iOS bundle id>.watchkitapp with WKCompanionAppBundleIdentifier pointing at the phone app, a watchOS 10.0 deployment target, a row in .modaal/project.json, and an entry in the Run picker. WatchRootView is a placeholder: the first watch feature replaces it.The Duet feature packages target iOS, so the watch app links none of them and its screens are written for the wrist rather than shared. Ask the agent for them the way you ask for any other screen.Forgot the chip? Ask the agent to add the watch target later — it takes the same path the chip takes.
Two templates outside the sentence. Watch only (watchos) is a standalone Apple Watch app with no iPhone app in the project; Watch + iPhone (watchos-ios-ribs) pairs a CombineRIBs iPhone app with an MV watch companion. Projects on either keep building and keep getting features.MV (Model-View) is a simplified MVVM: there is no ViewModel layer. Views observe @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.For a new project, the watch is the + Apple Watch chip on a Production app, which puts the watch target in a Duet tree.
A project folder and a git repository. There is no Xcode project and no scheme, so there is nothing to build or run until you add a target.The sentence does not create this: every combination scaffolds an app. A workspace with no app targets is what an imported repository is, and the agent is told when one is empty — ask it to add a target when you’re ready.
What was chosen is recorded in .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 → Production app, and the platform word decides which Duet flavor you get. 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. Android + iOS is part of Pro (plans on modaal.dev). Pick Android + iOS when Android is in scope now, or when you want both stores at launch. Pick iOS when Android is a “probably”, or when you want the iPhone app in users’ hands before taking on a second toolchain — its features are recorded and specced exactly as in the two-platform flavor, which is what makes the later port bounded work. No, and I’m certain → question 2.
Platform reach is a creation-time decision. A template fixes the architecture for the life of the project. A Quick prototype (MVVM) project has no migration path to Duet — Android there means a second project, written again. A CombineRIBs project does have one, taken feature by feature, but it is work a Production app avoids entirely. When Android is even a maybe, Production app costs less.

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 → Production app, even when only iPhone is planned. Production app for iOS is the production template for an Apple-only product.
A Quick prototype project is a throwaway by design. MVVM has no migration path in Modaal: going to production means starting a second project and carrying the behavior across by hand. Before picking it for something that “might eventually go to production”, ask whether you are ready to write it again. If the answer is “probably not” or “I hadn’t thought about that”, pick Production app.
The extra structure a production template needs is written by the agent, not by you — generating a Duet feature and generating an MVVM screen differ by seconds of agent time. What differs later is the cost of having picked wrong, and you pay that one.

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. It is the only template 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 reach the wrist?

Turn the + Apple Watch chip on, and keep the platform word set to where the phone app ships. The watch app is a companion target in the same project, on the same Duet architecture, and the chip is the only way to get one at creation — the kind stays Production app even when the wrist is the part you care about.

Which template for which situation?

CombineRIBs projects

The Production app kind in the sentence creates a Duet project. The template that used to carry that name, ios-ribs, 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 sentence does not create it, because the two Duet combinations cover every app it scaffolds — with a recorded behavior corpus and a route to Android on top. 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 template scaffolds a SpriteKit scene host, so 2D game, which is RIBs with a SpriteKit view layer, is reachable from the sentence and is the answer 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 Production app means that migration is never needed.

When to pick 2D game

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 kind 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 from SKNode subclasses rather than from your existing SwiftUI views.
RIBs → RIBs + SpriteKit is additive. The RIB tree, the modules and the tests keep their shape; what gets added is the SpriteKit packages and a scene host. So an existing CombineRIBs project that needs a drawn screen can ask the agent to add the SpriteKit surface at that point.A Duet project has no equivalent move — no Duet template scaffolds a scene host. If the product is a game, pick 2D game at creation and take CombineRIBs with it.
Building 2D games with SpriteKit walks through what the template gives you and what the first turn looks like.

Picking a template for a real app

Context. Max wants a simple interval training app. Three screens: workout templates, interval settings, and the timer itself with start / pause / stop. A personal project he might share with friends. No cloud — data stored locally in SwiftData.Prototype or product? A personal utility. Even on the App Store it’s unlikely to reach 50 screens. No Android plans, three screens, linear navigation (list → settings → timer). Nothing points at a production template.Decision: Quick prototype (MVVM). A production template would add per-feature module structure — and, on a Duet template, a spec and a recorded fixture corpus per feature — that three linear screens never use. MVVM covers the whole app: a 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.
Context. A fintech building a mobile bank: registration with KYC verification, balances, P2P and IBAN transfers, filtered transaction history, profile settings, push notifications, biometric auth. Android planned in six months.Prototype or product? Production. A regulated industry, real money, and a codebase that will be maintained for years.
  • Android in six months. Production app for Android + iOS is the only combination 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.
Decision: Production app for Android + iOS (Duet, Kotlin Multiplatform). This isn’t about team size — it is the right pick for a solo developer with an agent, because the agent generates each feature against a clear contract and the fixture gate catches a behavior change on the commit that caused it.Takeaway. When a product must exist on both stores and is judged on whether it behaves correctly, write the behavior once and let CI prove the two apps agree.
Context. A solo founder building a marketplace for plumbers, electricians and cleaners: an offerings feed, provider profiles, booking with a date/time picker, in-app chat, Stripe payments, order history, user profile. Android is coming, but “not now.” The business model anticipates growth.Prototype or product? There’s a business model with payments and growth plans — production.
  • “Android not now” commonly becomes an urgent requirement 6–8 months after launch. On a Production app 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.
Decision: Production app for iOS (Duet, Swift flavor). One toolchain and one set of screens to launch with, and every feature recorded and specced as it is written. When Android is scheduled, the Kotlin core and Android shell are added to the same repository and features cross one at a time, each verified against fixtures recorded months earlier.The line between the two platform words: if Android has to ship within roughly six months, pick Android + iOS at creation instead — the Android app is then built alongside the iPhone one rather than ported after it.Takeaway. When a project shows signs of growth — a business model, non-trivial navigation, possible Android — pick Production app from the start. Which platform word depends on when Android has to exist, not on whether it will.
Context. An internal app for ~500 employees: contact directory, office map, meeting-room booking, news feed. Tight budget, results needed fast. Data from a corporate REST API. No Android — everyone has an iPhone.Prototype or product? Less clear-cut than the others. It’s used in production, but it doesn’t grow — the scope is fixed. It will never become a marketplace or a social network. Navigation is linear: tab bar → list → detail, with booking as a simple form sheet.Decision: Quick prototype (MVVM). Fixed scope, linear navigation, no Android. A production template would add module structure the fixed scope never uses.The distinction that matters: the key question in the decision tree isn’t “will this be in production?” — it’s “will it grow?” A corporate directory with a clear spec doesn’t. If someone asks for vacation requests and expense tracking a year later, that’s when you decide whether the next version starts as a new Production app project. Deciding now, just in case, is premature optimization.Takeaway. Not every “real” app needs a production architecture. Bounded scope means MVVM delivers a better result in less time — corporate projects included.
Context. Anna built a habit tracker — three screens: habit list, add habit, stats. She picked Quick prototype, correctly, and the agent generated a working version in hours. She shipped it and started gaining users.Four months later: 50,000 users, an investor interested, and new requirements — social challenges, a subscription paywall, CloudKit sync, widgets, a watch companion, five languages.What she’s facing. This is the classic “throwaway prototype that accidentally became the product.” Two options:
  • A — Evolve the MVVM codebase. Keep adding features, and for complex flows (paywall, challenge onboarding) add lightweight flow coordinators: @Observable state 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 Production app. 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 template, 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.
Recommendation. If a few days can be set aside: B, now, while the app is three screens plus the new requirements rather than thirty. With an agent that rebuild is days of work, and it is the point at which Android stops being a separate project.If there’s genuinely no time and the product is growing daily: A as a stopgap. Flow coordinators for the paywall and onboarding hold for a few months. Record the restart as the next planned piece of work — the longer it waits, the more screens it has to cover.Worth noting: widgets and watch targets are pure SwiftUI either way, so they carry across untouched.Takeaway. Two rules apply. At the start, be honest that Quick prototype means throwaway — if there’s even a 30% chance of growth, start on Production app. When it takes off, restart while the codebase is still small.

Changing your mind later

Not everything the sentence sets is permanent. Here’s what is: Everything else — features, screens, navigation, integrations, pivots — is ordinary work you do in chat. See Modes and prompts for how to drive it. The first turn writes a 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.
Need help deciding? Ask in our community — describe what you’re building and someone will sanity-check the combination with you.

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.
Last modified on September 22, 2026