# Three apps, one product

> How the iOS, Android and web apps stay one Moonspell product with no shared UI code, through generated contracts, one server API and per-feature specs checked by the parity script.

Moonspell is three apps that share no UI code: iOS (SwiftUI), Android (Compose) and web (React). The web app is public, in `src/web/` (client code) and `src/app/(app)/` (its routes). The iOS and Android apps are private. They stay the same product because they follow the same three things:

1. **Contracts** (`contracts/`): names, colours, metrics and every shared sentence, generated into each app.
2. **One server API**: the same routes, sessions and shapes for every app (see [Architecture](https://docs.moonspell.app/moonspell/architecture.md#the-api)). `X-Crescent-Client: ios | android | web` picks each version's export (see [Builds and versions](https://docs.moonspell.app/moonspell/builds.md#which-export-each-app-gets)).
3. **Feature specs** (`spec/features/`): what each feature does, one numbered behaviour per row, with a column per app.

Games are not part of this: a game is one source, exported once per platform, and what differs per platform belongs in the engine. See [Platform layer](https://docs.moonspell.app/engine/platform-layer.md).

## Contracts

| File | Holds |
|---|---|
| `contracts/identity.json` | The product's names, the share host and URL scheme |
| `contracts/tokens.json` | The look: colours, corner radii, the gutter, layout limits (the web app's one column is capped at `layout.maxWidth`, 1024) |
| `contracts/copy.json` | Every sentence the apps share, by key; `{name}` in a string marks a value filled in at run time |

`npm run contracts` (`contracts/gen.mjs`) writes them into each app. In a public clone it writes and checks the web files only, `src/web/generated/contracts.ts` and `src/web/generated/contracts.css`. Never edit a generated file.

```bash
npm run contracts              # regenerate
npm run contracts -- --check   # fail when a generated file is out of date
```

## Feature specs

Each file in `spec/features/` (`account.md`, `community.md`, `create.md`, `feed.md`, `home.md`, `play.md`, `share.md`) has one table:

| ID | Behaviour | iOS | Android | Web |
|---|---|---|---|---|
| PLAY-1 | Play runs the version's build for this platform | yes | yes | yes |

- `yes`: built on that app.
- `no`: not built yet. The notes under the table say why, or what stands in for it.
- `n/a`: does not apply to that app (an App Clip on Android).

`npm run parity` (`spec/parity.mjs`) prints, per app, what is built and which behaviours are missing.

## Changing something

| The change | Where it starts |
|---|---|
| A colour, a radius, the product's name, a sentence | `contracts/`, then `npm run contracts` |
| A behaviour (what a screen or button does) | A row in `spec/features/`, then each app, or the cell left `no` on purpose |
| A new server route or field | The server, then each app's API client (`src/web/api.ts` for the web) |
| How games run on a platform | The engine (`templates/crescent-game/src/engine/`), never a game or one app's shell |

Contributors outside the project can change the contracts, the specs and the web app. Changes that need the phone apps are built there by the maintainers; mark the iOS and Android cells `no` and say so in the notes.

## The web app

| Route | Screen |
|---|---|
| `/` | Home: latest played, your games |
| `/feed` | Every player's games |
| `/create` | The conversation with Nova (`?game=<id>` to change one of yours, `?help=<id>` to help build someone else's) |
| `/play/<buildId>` | The player, with the chat about the game beside it |
| `/settings` | Account and credits |

The web app runs in the server's Next.js project, so it shares the API's types and has no deployment of its own. Games are phone-sized in it and run in an `<iframe>` on a separate play origin (see [Builds and versions](https://docs.moonspell.app/moonspell/builds.md#playing-a-version-on-the-web)), talking to the app with `postMessage` (`src/web/play.ts`). The client is `src/web/api.ts`, the conversation store `src/web/chat.ts`, the player frame `src/web/GameFrame.tsx`. How the engine itself runs in a browser is in [Web](https://docs.moonspell.app/platforms/web.md).
