|
| 1 | +# AGENTS.md — Constellation Mobile SDK |
| 2 | + |
| 3 | +## Project Overview |
| 4 | + |
| 5 | +Kotlin Multiplatform (KMP) SDK for embedding Pega Constellation forms into mobile apps. |
| 6 | +Targets Android, iOS, JVM/Desktop. Group: `com.pega.constellation.sdk.kmp`. |
| 7 | + |
| 8 | +### Module Layout |
| 9 | + |
| 10 | +| Module | Purpose | |
| 11 | +|---|---| |
| 12 | +| `core` | Domain models, interfaces, component system (publishable) | |
| 13 | +| `engine-webview` | WebView-based engine with platform implementations (publishable) | |
| 14 | +| `engine-mock` | Mock engine for testing | |
| 15 | +| `ui-components-cmp` | Compose Multiplatform UI widgets (publishable) | |
| 16 | +| `ui-renderer-cmp` | Renderers bridging core components to UI (publishable) | |
| 17 | +| `test` | Integration test infrastructure and mocks | |
| 18 | +| `samples/` | Sample Android, desktop, and iOS apps | |
| 19 | +| `scripts/` | JavaScript bridge layer (pure ES modules, no bundler) | |
| 20 | + |
| 21 | +## Build Commands |
| 22 | + |
| 23 | +```bash |
| 24 | +# Full build |
| 25 | +./gradlew clean build |
| 26 | + |
| 27 | +# Build + publish to local Maven |
| 28 | +./gradlew clean publishToMavenLocal |
| 29 | + |
| 30 | +# Build a single module |
| 31 | +./gradlew :core:build |
| 32 | +./gradlew :engine-webview:build |
| 33 | +``` |
| 34 | + |
| 35 | +## Test Commands |
| 36 | + |
| 37 | +Tests require an Android emulator/device or iOS simulator. Download CDN fixtures first: |
| 38 | + |
| 39 | +```bash |
| 40 | +cd test/src/commonMain/composeResources/files/responses && ./download_js_files.sh |
| 41 | +``` |
| 42 | + |
| 43 | +### Android |
| 44 | + |
| 45 | +```bash |
| 46 | +# All SDK instrumented tests |
| 47 | +./gradlew :test:connectedAndroidTest |
| 48 | + |
| 49 | +# All sample app UI tests |
| 50 | +./gradlew :samples:android-cmp-app:connectedAndroidTest |
| 51 | + |
| 52 | +# Single test class |
| 53 | +./gradlew :test:connectedAndroidTest \ |
| 54 | + -Pandroid.testInstrumentationRunnerArguments.class=com.pega.constellation.sdk.kmp.test.ConstellationSdkTest |
| 55 | + |
| 56 | +# Single test method |
| 57 | +./gradlew :test:connectedAndroidTest \ |
| 58 | + -Pandroid.testInstrumentationRunnerArguments.class=com.pega.constellation.sdk.kmp.test.ConstellationSdkTest#test_initialization |
| 59 | +``` |
| 60 | + |
| 61 | +### iOS |
| 62 | + |
| 63 | +```bash |
| 64 | +xcodebuild -project samples/swiftui-components-app/UITest/UITest.xcodeproj \ |
| 65 | + -scheme UITest -destination "platform=iOS Simulator,name=<sim>" test |
| 66 | + |
| 67 | +# Single test |
| 68 | +xcodebuild ... -only-testing:"UITest/TestCaseProcessing/testCaseProcessing" test |
| 69 | +``` |
| 70 | + |
| 71 | +--- |
| 72 | + |
| 73 | +## Kotlin Code Style |
| 74 | + |
| 75 | +### Formatting & Imports |
| 76 | + |
| 77 | +- 4-space indentation (no tabs); `kotlin.code.style=official` in `gradle.properties` |
| 78 | +- One primary class/interface per file; file name matches the class name |
| 79 | +- No explicit `public` modifier — rely on Kotlin's default public visibility |
| 80 | +- Imports: alphabetically ordered, no blank-line separators, no wildcard imports |
| 81 | + - Exception: iOS platform APIs like `platform.WebKit.*` |
| 82 | + - Order: android/androidx → project (`com.pega.*`) → kotlin → kotlinx → third-party → java |
| 83 | + |
| 84 | +### Naming & Visibility |
| 85 | + |
| 86 | +- Classes/Interfaces: PascalCase (`FlowContainerComponent`) |
| 87 | +- Functions: camelCase; Composables: PascalCase; factory funcs: `for`/`create` prefix |
| 88 | +- Variables: camelCase; constants: SCREAMING_SNAKE_CASE in `companion object` |
| 89 | +- Enums: SCREAMING_SNAKE_CASE; value classes: PascalCase (`ComponentId`) |
| 90 | +- Test methods: `test_` prefix + snake_case (`test_initialization()`) |
| 91 | +- `internal` for impl classes; `private set` on mutable Compose state; no explicit `public` |
| 92 | + |
| 93 | +### Error Handling |
| 94 | + |
| 95 | +- **Sealed classes** for domain state: `State.Ready`, `State.Error`, `EngineEvent.Error` |
| 96 | +- **`EngineError` interface** with concrete types: `JsError`, `InternalError` |
| 97 | +- **`runCatching`/`onFailure`** for defensive handling around component updates |
| 98 | +- **Null safety with early returns**: `val x = y as? Type ?: return` |
| 99 | +- **`Log` object** (`Log.i`, `Log.w`, `Log.e`) for non-critical failures — never swallow silently |
| 100 | + |
| 101 | +### Patterns |
| 102 | + |
| 103 | +- `StateFlow`/`MutableStateFlow` for observable state; `SharedFlow` for events |
| 104 | +- `CoroutineScope(Dispatchers.Main + SupervisorJob())` for lifecycle-scoped coroutines |
| 105 | +- `mutableStateOf` (Compose) for component property state, not StateFlow |
| 106 | +- `fun interface` for SAM types: `EngineEventHandler`, `ComponentProducer` |
| 107 | +- `companion object` with `private const val TAG` and factory methods |
| 108 | +- `@JvmInline value class` for type-safe identifiers; `lateinit var` for deferred init |
| 109 | +- KDoc on public API interfaces/classes with `@param`/`@property` tags; minimal docs on internals |
| 110 | + |
| 111 | +--- |
| 112 | + |
| 113 | +## JavaScript Code Style (scripts/) |
| 114 | + |
| 115 | +The `scripts/` directory contains a pure ES module JavaScript codebase (no bundler, no npm). |
| 116 | +This is a bridge layer loaded into a WebView at runtime. |
| 117 | + |
| 118 | +### Formatting |
| 119 | + |
| 120 | +- 4-space indentation (`.prettierrc`: `tabWidth: 4, useTabs: false`) |
| 121 | +- Double quotes for strings; semicolons at end of statements |
| 122 | +- Max line length: 120 (`.editorconfig`) |
| 123 | +- Relative imports with explicit `.js` extension: `import { BaseComponent } from "../../base.component.js";` |
| 124 | +- No bare module specifiers, no npm packages; one import per line |
| 125 | + |
| 126 | +### File & Class Naming |
| 127 | + |
| 128 | +- Files: `kebab-case.component.js` (e.g., `text-input.component.js`) |
| 129 | +- Classes: PascalCase matching file name (e.g., `TextInputComponent`) |
| 130 | +- Component type string: PascalCase without "Component" suffix (`this.type = "TextInput"`) |
| 131 | + |
| 132 | +### Component Lifecycle |
| 133 | + |
| 134 | +All components extend `BaseComponent`. Required methods in order: |
| 135 | + |
| 136 | +1. **`constructor(componentsManager, pConn)`** — call `super(componentsManager, pConn)`, set `this.type` |
| 137 | +2. **`init()`** — register via `this.jsComponentPConnect.registerAndSubscribeComponent(this, this.checkAndUpdate)`, call `this.componentsManager.onComponentAdded(this)`, then `this.checkAndUpdate()` |
| 138 | +3. **`destroy()`** — call `super.destroy()`, unsubscribe, call `this.componentsManager.onComponentRemoved(this)` |
| 139 | +4. **`update(pConn, ...)`** — guard with equality check, reassign, call `this.checkAndUpdate()` |
| 140 | +5. **`checkAndUpdate()`** — call `this.jsComponentPConnect.shouldComponentUpdate(this)`, if true call `this.#updateSelf()` |
| 141 | +6. **`#updateSelf()`** — resolve props from `this.pConn`, build state, call `this.#sendPropsUpdate()` |
| 142 | +7. **`#sendPropsUpdate()`** — set `this.props = { ... }`, call `this.componentsManager.onComponentPropsUpdate(this)` |
| 143 | + |
| 144 | +### Key Conventions |
| 145 | + |
| 146 | +- **Private methods** use `#` prefix: `#updateSelf()`, `#sendPropsUpdate()` |
| 147 | +- **No TypeScript** in new code — the `.ts` files are legacy Angular originals kept as reference |
| 148 | +- Component registration: `scripts/dxcomponents/mappings/sdk-pega-component-map.js` |
| 149 | +- TAG-based logging: `const TAG = "ClassName";` with `Utils.log(TAG, ...)` or `console.warn` |
| 150 | +- Guard clauses with early return for null/undefined checks |
| 151 | +- `try/catch` only at system boundaries (bridge calls, JSON parsing) |
| 152 | +- String interpolation with template literals: `` `@P .${context}` `` |
| 153 | + |
| 154 | +### TS → JS Migration Rules |
| 155 | + |
| 156 | +When migrating `.ts` (Angular) components to `.js`: |
| 157 | +1. Remove all Angular imports, decorators (`@Component`, `@Input`), and TypeScript types |
| 158 | +2. Remove interfaces and type annotations |
| 159 | +3. Remove all Angular lifecycle interfaces |
| 160 | +4. For simple fields components extend `FieldBaseComponent`" if possible and reasonable. |
| 161 | +5. For Container components extend `ContainerBaseComponent` if possible and reasonable. |
| 162 | +6. For rest of components extend `BaseComponent` |
| 163 | +7. `ngOnInit` → `init()` with `componentsManager.onComponentAdded(this)` |
| 164 | +8. `ngOnDestroy` → `destroy()` with `super.destroy()` and `componentsManager.onComponentRemoved(this)` |
| 165 | +9. Merge `onStateChange` into `checkAndUpdate()` |
| 166 | +10. `updateSelf` → `#updateSelf()` (private); add `#sendPropsUpdate()` at end |
| 167 | +11. Add `update(pConn, ...)` method for external re-rendering |
| 168 | +12. Replace `this.pConn$` → `this.pConn`; remove all `as any` casts |
| 169 | +13. Normalize quotes to double quotes |
| 170 | +14. If there is some external dependency try to look in additional provided ts files and copy its content to migrated js. If nothing is found try to find existing equivalent in js files or create mocked implementation. |
0 commit comments