Skip to content

Commit 0b8142b

Browse files
BUG-988267: fix components destroy flow (#29)
* BUG-988267: optimizing AGENTS.md * BUG-988267: fixing destroy in few components refactoring components so that all children components are sent in "props.children" - part 1 * BUG-988267: refactoring modal view container component fixing embedded data component * BUG-988267: moving destroy content to ContainerBaseComponent More refactor of moving children components to props.children * BUG-988267: adding timeout cleanup for field group and flow container * BUG-988267: adding guard for async actions so that they are not executed if component is not alive anymore * BUG-988267: refactoring field group template so that it extends container component * BUG-988267: refactoring simple table template so that it extends container component * BUG-988267: refactoring simple table manual template so that it extends container component * BUG-988267: changing swift ui components to align with changes * BUG-988267: fixing constellation tests * BUG-988267: refactoring field group template and simpla table manual to be more readable * BUG-988267: review fixes
1 parent 0c87912 commit 0b8142b

52 files changed

Lines changed: 605 additions & 715 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

AGENTS.md

Lines changed: 4 additions & 176 deletions
Original file line numberDiff line numberDiff line change
@@ -1,180 +1,8 @@
11
# AGENTS.md — Constellation Mobile SDK
22

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

ai-instructions/AGENTS.md

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

core/src/commonMain/kotlin/com/pega/constellation/sdk/kmp/core/api/Component.kt

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -60,16 +60,10 @@ abstract class BaseComponent(
6060
it.parentId = context.id
6161
}
6262

63-
64-
@Suppress("UNCHECKED_CAST")
65-
internal fun <T: Component> adoptChildAndGetTyped(childId: ComponentId) =
66-
adoptChildAndGet(childId) as? T
67-
6863
protected fun <T> JsonArray.mapWithIndex(transform: JsonArray.(Int) -> T) =
6964
List(size) { this.transform(it) }
7065
}
7166

7267
interface HideableComponent {
7368
val visible: Boolean
7469
}
75-

0 commit comments

Comments
 (0)