|
| 1 | +# AGENTS.md |
| 2 | + |
| 3 | +## Purpose |
| 4 | + |
| 5 | +This repository is the Amazon App Platform: a Kotlin Multiplatform application framework plus example applications and a starter blueprint. The core concepts are documented in [`docs/`](docs/) and implemented across reusable library modules plus a few app entrypoints. |
| 6 | + |
| 7 | +Start here before changing code: |
| 8 | + |
| 9 | +- `README.md` |
| 10 | +- `docs/index.md` |
| 11 | +- `docs/setup.md` |
| 12 | +- `docs/module-structure.md` |
| 13 | +- `docs/di.md` |
| 14 | +- `docs/presenter.md` |
| 15 | +- `docs/renderer.md` |
| 16 | +- `docs/template.md` |
| 17 | +- `docs/testing.md` |
| 18 | +- `settings.gradle` |
| 19 | +- `buildSrc/src/main/kotlin/software/amazon/app/platform/gradle/buildsrc/` |
| 20 | + |
| 21 | +`mkdocs.yml` is the docs site manifest. The Pages workflow builds Wasm artifacts for `:sample:app` and `:recipes:app` and copies them into `docs/web/` before publishing. |
| 22 | + |
| 23 | +## Repo Shape |
| 24 | + |
| 25 | +Important top-level areas: |
| 26 | + |
| 27 | +- `gradle-plugin/`: the published `software.amazon.app.platform` Gradle plugin. |
| 28 | +- `buildSrc/`: repo-local convention plugins used by this repository’s own modules. This is where platform targets, emulator config, desktop packaging, and Wasm defaults are defined. |
| 29 | +- `docs/`: framework documentation. Treat this as the authoritative product docs. |
| 30 | +- `sample/`: the main sample app. This is the best place to study end-to-end usage of scopes, DI, presenters, renderers, templates, fakes, and robots. |
| 31 | +- `recipes/`: a second example app plus reusable “recipe” patterns, including the separate `recipesIosApp` SwiftUI/Xcode wrapper. |
| 32 | +- `blueprints/starter/`: a standalone starter app template with its own Gradle wrapper, version catalog, and README. |
| 33 | + |
| 34 | +Core framework module families: |
| 35 | + |
| 36 | +- `scope`, `di-common` |
| 37 | +- `presenter`, `presenter-molecule` |
| 38 | +- `renderer`, `renderer-android-view`, `renderer-compose-multiplatform` |
| 39 | +- `robot`, `robot-compose-multiplatform`, `robot-internal` |
| 40 | +- `kotlin-inject`, `kotlin-inject-extensions` |
| 41 | +- `metro`, `metro-extensions` |
| 42 | +- `ksp-common` |
| 43 | + |
| 44 | +## Architecture Rules |
| 45 | + |
| 46 | +The most important repo rule is the module structure documented in `docs/module-structure.md`. |
| 47 | + |
| 48 | +- `:public` modules expose reusable APIs and shared code. |
| 49 | +- `:impl` modules contain concrete implementations. |
| 50 | +- `:testing` modules hold shared fakes and test helpers. |
| 51 | +- `:*-robots` modules hold shared UI robots. |
| 52 | +- `:app` modules are the only modules allowed to depend on `:impl` modules. |
| 53 | + |
| 54 | +Do not introduce a dependency from a non-`:app` module to an `:impl` module. The build enforces this via `checkModuleStructureDependencies`. |
| 55 | + |
| 56 | +The framework’s architectural flow is: |
| 57 | + |
| 58 | +1. `Scope` and DI assemble objects for a lifecycle boundary. |
| 59 | +2. `MoleculePresenter` implementations produce models. |
| 60 | +3. App-specific `Template` presenters wrap the root model tree. |
| 61 | +4. `RendererFactory` resolves platform renderers for those models. |
| 62 | +5. Thin platform entrypoints bootstrap the root scope and start rendering. |
| 63 | + |
| 64 | +Representative entrypoints: |
| 65 | + |
| 66 | +- Android: `sample/app/src/androidMain/.../AndroidApplication.kt`, `MainActivity.kt` |
| 67 | +- iOS: `sample/app/src/iosMain/.../MainViewController.kt`, `sample/iosApp/` |
| 68 | +- Desktop: `sample/app/src/desktopMain/.../Main.kt`, `DesktopApp.kt` |
| 69 | +- Wasm: `sample/app/src/wasmJsMain/.../Main.kt` |
| 70 | + |
| 71 | +## Toolchain |
| 72 | + |
| 73 | +Local development should match CI as closely as possible. These versions live in `gradle/libs.versions.toml`. |
| 74 | + |
| 75 | +Expected warning: Gradle prints a warning that configuration-on-demand is not supported for Wasm targets. This is noisy but currently normal in this repo. |
| 76 | + |
| 77 | +## Run The Apps |
| 78 | + |
| 79 | +There are three app-style entrypoints to care about: |
| 80 | + |
| 81 | +- `:sample:app`: main sample app inside the root build. |
| 82 | +- `:recipes:app`: recipe/demo app inside the root build. |
| 83 | +- `blueprints/starter`: standalone starter app; run commands from inside that directory or use its own `./gradlew`. |
| 84 | + |
| 85 | +### Android |
| 86 | + |
| 87 | +Install the debug APK onto a connected device or emulator: |
| 88 | + |
| 89 | +```bash |
| 90 | +./gradlew :sample:app:installDebug |
| 91 | +./gradlew :recipes:app:installDebug |
| 92 | +``` |
| 93 | + |
| 94 | +For the standalone starter: |
| 95 | + |
| 96 | +```bash |
| 97 | +cd blueprints/starter |
| 98 | +./gradlew :app:installDebug |
| 99 | +``` |
| 100 | + |
| 101 | +`buildSrc/.../BaseAndroidPlugin.kt` configures managed emulator tests with a local device named `emulator` using a Pixel 3 / API 30 `aosp-atd` image. |
| 102 | + |
| 103 | +### iOS |
| 104 | + |
| 105 | +Sample app: |
| 106 | + |
| 107 | +```bash |
| 108 | +open sample/iosApp/iosApp.xcodeproj |
| 109 | +``` |
| 110 | + |
| 111 | +Recipe app: |
| 112 | + |
| 113 | +```bash |
| 114 | +open recipes/recipesIosApp/recipesIosApp.xcodeproj |
| 115 | +``` |
| 116 | + |
| 117 | +The Xcode projects include a shell build phase that calls Gradle: |
| 118 | + |
| 119 | +- `:sample:app:embedAndSignAppleFrameworkForXcode` |
| 120 | +- `:recipes:app:embedAndSignAppleFrameworkForXcode` |
| 121 | + |
| 122 | +If you only want to build the Kotlin framework without opening Xcode: |
| 123 | + |
| 124 | +```bash |
| 125 | +./gradlew :sample:app:linkDebugFrameworkIosSimulatorArm64 |
| 126 | +./gradlew :recipes:app:linkDebugFrameworkIosSimulatorArm64 |
| 127 | +``` |
| 128 | + |
| 129 | +CI builds the sample iOS wrapper with `xcodebuild -project sample/iosApp/iosApp.xcodeproj -scheme iosApp ... -destination id=<simulator-id>`. Use `xcrun simctl list devices` to pick a simulator if you need a pure CLI invocation. |
| 130 | + |
| 131 | +### Desktop |
| 132 | + |
| 133 | +Run the desktop Compose app: |
| 134 | + |
| 135 | +```bash |
| 136 | +./gradlew :sample:app:run |
| 137 | +./gradlew :recipes:app:run |
| 138 | +``` |
| 139 | + |
| 140 | +Starter blueprint: |
| 141 | + |
| 142 | +```bash |
| 143 | +cd blueprints/starter |
| 144 | +./gradlew :app:run |
| 145 | +``` |
| 146 | + |
| 147 | +Desktop packaging tasks such as `packageDmg`, `packageDeb`, and `packageMsi` are available on app modules. |
| 148 | + |
| 149 | +### Wasm |
| 150 | + |
| 151 | +Development server: |
| 152 | + |
| 153 | +```bash |
| 154 | +./gradlew :sample:app:wasmJsBrowserDevelopmentRun |
| 155 | +./gradlew :recipes:app:wasmJsBrowserDevelopmentRun |
| 156 | +``` |
| 157 | + |
| 158 | +Production bundle: |
| 159 | + |
| 160 | +```bash |
| 161 | +./gradlew :sample:app:wasmJsBrowserDistribution |
| 162 | +./gradlew :recipes:app:wasmJsBrowserDistribution |
| 163 | +``` |
| 164 | + |
| 165 | +Starter blueprint: |
| 166 | + |
| 167 | +```bash |
| 168 | +cd blueprints/starter |
| 169 | +./gradlew :app:wasmJsBrowserDevelopmentRun |
| 170 | +``` |
| 171 | + |
| 172 | +After a production Wasm build, serve the generated files from: |
| 173 | + |
| 174 | +- `sample/app/build/dist/wasmJs/productionExecutable/` |
| 175 | +- `recipes/app/build/dist/wasmJs/productionExecutable/` |
| 176 | + |
| 177 | +The starter README suggests `npx http-server` from the production output directory. |
| 178 | + |
| 179 | +## Run The Tests |
| 180 | + |
| 181 | +### Repo-wide CI-style checks |
| 182 | + |
| 183 | +These are the main root-level quality gates used by GitHub Actions: |
| 184 | + |
| 185 | +```bash |
| 186 | +./gradlew testDebugUnitTest |
| 187 | +./gradlew iosSimulatorArm64Test -Pkotlin.incremental.native=true |
| 188 | +./gradlew desktopTest |
| 189 | +./gradlew linuxX64Test |
| 190 | +./gradlew wasmJsTest |
| 191 | +./gradlew apiCheck |
| 192 | +./gradlew ktfmtCheck |
| 193 | +./gradlew detekt |
| 194 | +./gradlew lint |
| 195 | +./gradlew checkModuleStructureDependencies |
| 196 | +``` |
| 197 | + |
| 198 | +### Sample app tests by platform |
| 199 | + |
| 200 | +Android instrumented UI tests: |
| 201 | + |
| 202 | +```bash |
| 203 | +./gradlew :sample:app:emulatorCheck |
| 204 | +``` |
| 205 | + |
| 206 | +Or against a manually started device: |
| 207 | + |
| 208 | +```bash |
| 209 | +./gradlew :sample:app:connectedDebugAndroidTest |
| 210 | +``` |
| 211 | + |
| 212 | +Desktop UI tests: |
| 213 | + |
| 214 | +```bash |
| 215 | +./gradlew :sample:app:desktopTest |
| 216 | +``` |
| 217 | + |
| 218 | +Android unit tests: |
| 219 | + |
| 220 | +```bash |
| 221 | +./gradlew :sample:app:testDebugUnitTest |
| 222 | +``` |
| 223 | + |
| 224 | +iOS simulator tests: |
| 225 | + |
| 226 | +```bash |
| 227 | +./gradlew :sample:app:iosSimulatorArm64Test -Pkotlin.incremental.native=true |
| 228 | +``` |
| 229 | + |
| 230 | +All sample app target tests: |
| 231 | + |
| 232 | +```bash |
| 233 | +./gradlew :sample:app:allTests |
| 234 | +``` |
| 235 | + |
| 236 | +### Where tests live |
| 237 | + |
| 238 | +- Android UI tests: `sample/app/src/androidInstrumentedTest/` |
| 239 | +- Desktop UI tests: `sample/app/src/desktopTest/` |
| 240 | +- Shared unit tests: `sample/*/src/commonTest/` |
| 241 | +- Shared fakes: `sample/user/testing/` |
| 242 | +- Shared robots: `sample/login/impl-robots/`, `sample/user/impl-robots/` |
| 243 | + |
| 244 | +## Current Test Reality |
| 245 | + |
| 246 | +As of this checkout: |
| 247 | + |
| 248 | +- `:sample:app:desktopTest` runs successfully. |
| 249 | +- `:sample:app:testDebugUnitTest` succeeds but currently has `NO-SOURCE`. |
| 250 | +- `:sample:app:iosSimulatorArm64Test -Pkotlin.incremental.native=true` succeeds but is currently skipped because `sample/app` has no iOS test sources. |
| 251 | +- Android UI coverage for the sample app is in `androidInstrumentedTest` and is exercised through `emulatorCheck`/`connectedDebugAndroidTest`. |
| 252 | + |
| 253 | +## Wasm Lockfile Caveat |
| 254 | + |
| 255 | +Wasm tasks are currently strict about the committed Yarn lockfile under `kotlin-js-store/wasm/yarn.lock`. |
| 256 | + |
| 257 | +If a Wasm task fails with: |
| 258 | + |
| 259 | +```text |
| 260 | +Execution failed for task ':kotlinWasmStoreYarnLock'. |
| 261 | +Lock file was changed. Run the `kotlinWasmUpgradeYarnLock` task to actualize lock file |
| 262 | +``` |
| 263 | + |
| 264 | +then the generated `build/wasm/yarn.lock` does not match the committed lock. In this checkout, both `:sample:app:wasmJsTest` and `:sample:app:wasmJsBrowserDistribution` hit that failure. |
| 265 | + |
| 266 | +Treat `kotlinWasmUpgradeYarnLock` as an intentional dependency update step, not a routine run command. If you change Wasm/npm dependencies on purpose, update and review `kotlin-js-store/wasm/yarn.lock` in the same change. |
| 267 | + |
| 268 | +## Docs Workflow |
| 269 | + |
| 270 | +To work on docs locally: |
| 271 | + |
| 272 | +```bash |
| 273 | +cp CHANGELOG.md docs/changelog.md |
| 274 | +pip install mkdocs-material "mkdocs-material[imaging]" |
| 275 | +mkdocs serve |
| 276 | +``` |
| 277 | + |
| 278 | +When changing framework behavior, update both: |
| 279 | + |
| 280 | +- the relevant `docs/*.md` page |
| 281 | +- the sample and/or starter code that demonstrates that behavior |
| 282 | + |
| 283 | +If a change affects how consumers start a new project, also update `blueprints/starter/README.md`. |
0 commit comments