@hungvv/super-ads-skills 1.0.0 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. package/README.md +71 -13
  2. package/dist/cli.js +8 -2
  3. package/package.json +1 -1
  4. package/skills/README.md +4 -0
  5. package/skills/adspro-to-superads/SKILL.md +157 -732
  6. package/skills/adspro-to-superads/references/global-invariant-check.md +49 -0
  7. package/skills/adspro-to-superads/references/migration-contract.md +57 -0
  8. package/skills/adspro-to-superads/references/phase-0-inventory.md +39 -0
  9. package/skills/adspro-to-superads/references/phase-1-2-gradle-manifest.md +85 -0
  10. package/skills/adspro-to-superads/references/phase-3-config-migration.md +159 -0
  11. package/skills/adspro-to-superads/references/phase-4-wrapper-observability.md +70 -0
  12. package/skills/adspro-to-superads/references/phase-5-7-callsites-layout-cleanup.md +73 -0
  13. package/skills/adspro-to-superads/references/principles-and-authority.md +73 -0
  14. package/skills/adspro-to-superads/references/user-questions.md +53 -0
  15. package/skills/adspro-to-superads/snippets/ads-api-quick.md +20 -0
  16. package/skills/adspro-to-superads/templates/migration-inventory.md +42 -0
  17. package/skills/super-ads-ab-test/SKILL.md +91 -23
  18. package/skills/super-ads-adb-test/SKILL.md +85 -36
  19. package/skills/super-ads-adb-test/{references → examples}/scenarios-sample.md +5 -5
  20. package/skills/super-ads-add-placement/SKILL.md +145 -50
  21. package/skills/super-ads-add-placement/references/call-site-api.md +33 -0
  22. package/skills/super-ads-add-placement/snippets/app-open.md +25 -0
  23. package/skills/super-ads-add-placement/snippets/fullscreen-nav.md +14 -0
  24. package/skills/super-ads-add-placement/snippets/inline-banner-native.md +19 -0
  25. package/skills/super-ads-add-placement/snippets/preload-event.md +13 -0
  26. package/skills/super-ads-add-placement/snippets/reward.md +17 -0
  27. package/skills/super-ads-config/SKILL.md +94 -36
  28. package/skills/super-ads-debug/SKILL.md +87 -37
  29. package/skills/super-ads-debug/references/logging-setup.md +2 -0
  30. package/skills/super-ads-debug/scripts/filter-logads.sh +21 -0
  31. package/skills/super-ads-debug-override/SKILL.md +154 -81
  32. package/skills/super-ads-debug-override/snippets/arm-override.md +9 -0
  33. package/skills/super-ads-debug-override/snippets/check-module.md +8 -0
  34. package/skills/super-ads-debug-override/snippets/clear-override.md +9 -0
  35. package/skills/super-ads-debug-override/snippets/confirm-active.md +16 -0
  36. package/skills/super-ads-guide/SKILL.md +129 -0
  37. package/skills/super-ads-guide/references/compatibility.md +26 -0
  38. package/skills/super-ads-guide/references/sdk-version-check.md +50 -0
  39. package/skills/super-ads-integrate/SKILL.md +145 -47
  40. package/skills/super-ads-integrate/references/application-setup.md +2 -21
  41. package/skills/super-ads-integrate/references/gradle-setup.md +1 -13
  42. package/skills/super-ads-integrate/snippets/consent-request.md +12 -0
  43. package/skills/super-ads-integrate/templates/application-oncreate.md +17 -0
  44. package/skills/super-ads-integrate/templates/gradle-dependencies.md +17 -0
  45. package/skills/super-ads-release-check/SKILL.md +85 -32
  46. package/skills/super-ads-show-rate/SKILL.md +87 -36
  47. package/skills/super-ads-show-rate/references/collect-snippet.md +2 -36
  48. package/skills/super-ads-show-rate/scripts/show-rate.sh +25 -0
  49. package/skills/super-ads-show-rate/snippets/show-events-logger.md +26 -0
  50. package/skills/super-ads-upgrade/SKILL.md +137 -0
  51. package/skills/super-ads-upgrade/references/upgrade-hops.md +26 -0
  52. package/skills/super-ads-upgrade/templates/changelog-template.md +39 -0
  53. package/skills/super-ads-add-placement/references/call-site-snippets.md +0 -90
  54. /package/skills/super-ads-ab-test/{references → templates}/firebase-handoff-template.md +0 -0
  55. /package/skills/super-ads-config/{references → examples}/patch-examples.md +0 -0
@@ -0,0 +1,49 @@
1
+ # Global Invariant Check
2
+
3
+ Chạy sau Phase 7, **trước** build. Đọc [verification.md](verification.md).
4
+
5
+ ```text
6
+ COUNT(unique AdsPro spaceName) <= COUNT(SuperAds placements)
7
+ AdsPro spaceName \ SuperAds placements = rỗng
8
+ SuperAds placements \ AdsPro spaceName ⊆ Bảng D reload extras ∪ Documented exclusions
9
+ COUNT(AdsPro ids toàn bộ) == COUNT(SuperAds units toàn bộ)
10
+ (gộp unit trùng network+id chỉ khi product yêu cầu; DUPLICATE_UNIT_ID = warning)
11
+
12
+ COUNT(call site load() cũ) == COUNT(hàng Bảng B đã map)
13
+ mọi chuỗi preload.eventActive ⊆ tập EventScreen app này thực sự emit
14
+
15
+ mọi adItem NATIVE_COLLAPSIBLE có đủ 2 key layout, và collapsed != expanded
16
+ mọi ui.layoutAdName* trỏ tới file XML tồn tại
17
+ mọi network trong adItem.units có key layout tương ứng
18
+
19
+ mọi reload.*.placementID trỏ tới placement tồn tại
20
+ mọi placement có show.timeAutoReloadMs đều có reload.auto.placementID
21
+ không placement chain nào mang reload.auto hoặc reload.dismiss
22
+ mọi space reload AdsPro **có call site** là đích của đúng một reload.*.placementID
23
+ zero-call-site reload → Documented exclusions
24
+ nếu Bảng D: layout đích == layout show; unit == unit AdsPro lúc click
25
+ không gộp ≥2 layout nguồn khác nhau vào một adItem reload
26
+
27
+ file Ads.init / configProvider == file JSON đang migrate
28
+ nếu có chain: len(native) + firstShowAt/showEvery khớp đo app này
29
+ mọi INTER fullscreen: mode.navigate từ callback app, không default SDK
30
+
31
+ mọi file màn hình bị sửa có lý do thuộc bảng 5b
32
+ mọi tham số inert đã ghi vào bảng Inert parameters
33
+ không có file utils ads mới nào được tạo ngoài quyết định nhóm C
34
+
35
+ ba mối observability đã nối: logger sink, tracker collector, config provider
36
+ không dispatchEvent / app open nào trước callback consent
37
+ mọi tên event analytics cũ đều có nhà mới (tracking.event* trong JSON, hoặc collector app-side)
38
+ không tên event analytics nào bị mất mà không ghi vào Decisions
39
+ ```
40
+
41
+ Ngoại lệ chỉ hợp lệ khi ghi vào "Documented exclusions".
42
+
43
+ Sau invariant: build → Toast `UpdateJson` version (bước đầu tiên) → `ConfigRegistryCheck` sạch →
44
+ fill từng placement → semantic matrix → 13 chuỗi `adType` analytics không đổi.
45
+
46
+ 13 chuỗi khoá bởi `AdProfileMappingTest`: `INTER`, `NATIVE`, `NATIVE_COLLAPSIBLE`,
47
+ `NATIVE_FULL_ACTIVITY`, `APP_OPEN`, `BANNER`, `BANNER320x250`, `BANNER_COLLAPSIBLE_TOP`,
48
+ `BANNER_COLLAPSIBLE_BOTTOM`, `REWARD`, `REWARD_INTER`, `NATIVE_CHAIN`,
49
+ `INTERSTITIAL_NATIVE_CHAIN`. Đổi chuỗi ⇒ dashboard doanh thu trắng, không có gì fail.
@@ -0,0 +1,57 @@
1
+ # Migration Contract và Stop Conditions
2
+
3
+ MUST:
4
+
5
+ - Placement name giữ **nguyên văn** spaceName cũ, kể cả sai chính tả
6
+ (`Native_Trennding`, `Native_Litst_Face_Filter`, `Native_WellcomeBack`).
7
+ **MUST NOT CHANGE.** Dashboard doanh thu, Firebase RC và analytics đang query đúng chuỗi đó.
8
+ - `adsType` preserve đúng format semantics theo bảng trong `api-mapping.md`.
9
+ - Mediation adapter `com.google.ads.mediation:*` hiện có **giữ nguyên**.
10
+ - App open setup **đúng một lần** trong wrapper `init`.
11
+ - Firebase RC key ads giữ nguyên chuỗi.
12
+ - Mỗi thời điểm preload cũ ứng **đúng một** màn/thời điểm trong `preload.eventActive` (một chuỗi,
13
+ hoặc cặp `_fo` + `_fn` của **cùng** thời điểm nếu code cũ preload vô điều kiện — xem 3b).
14
+ - Consent (CMP) **trước** mọi preload / show / app open: wrapper chỉ dispatch event và mở app open
15
+ sau callback `showCmp` (`MIGRATION.md:126-127` — core không tự chặn request khi chưa có consent).
16
+ - `mode.navigate` theo **từng** placement, đọc callback AdsPro của **app này**. Default SDK `NAVIGATE_ON_DISMISS` — không copy template/SlideShow.
17
+ - Reload → `reload.*` trên `placements`. Layout + unit là của **placement đích** (`ReloadCoordinator`). Bảng D khi có click reload.
18
+ - File JSON migrate **trùng** `Ads.init` / `configProvider` của app này.
19
+ - Nếu có native-sau-inter: `firstShowAt` / `showEvery` / `adItems.native[]` đo từ counter + số dialog **production của app này** (công thức SDK `ChainAdActivity`), không copy app khác / không đếm space JSON.
20
+ - Hàm gọi ad gốc **giữ nguyên chữ ký**; wrapper ở đúng file cũ.
21
+ - Nối đủ ba mối observability trước khi verify: logger sink, tracker collector, config provider.
22
+
23
+ MUST NOT:
24
+
25
+ - Đổi / chuẩn hoá / sửa chính tả placement name.
26
+ - Tạo abstraction mới, hay tách wrapper thành nhiều file helper.
27
+ - Port *logic* của `AdsUtils.kt` cũ (chữ ký thì giữ).
28
+ - Xoá mediation adapter vì "SuperAds đã có sẵn".
29
+ - Gọi `Ads.preload*` thủ công thay `preload.eventActive`.
30
+ - Tự viết timer / listener reload ở app — `ReloadCoordinator` trong core đã làm.
31
+ - Sửa màn hình khi không có lý do thuộc danh sách Phase 5b.
32
+ - Dọn tham số inert trong cùng commit migration.
33
+ - Tạo `LegacyAdCallbackAdapter` hay bất kỳ lớp ép 14 callback AdsPro thành 7 callback SuperAds.
34
+ - Gán `mode.navigate` hàng loạt từ template / default SDK.
35
+ - Gộp nhiều layout click khác nhau vào một adItem reload (P2) — hoặc tách bừa khi AdsPro đã tách space.
36
+ - `adItems.native[]` / `showEvery` copy từ template hoặc từ app đã migrate khác.
37
+ - Reload đích = space zero call site chỉ vì tên có `Reload`.
38
+ - Sửa JSON A trong khi init / RC đọc JSON B.
39
+
40
+ ## Stop Conditions
41
+
42
+ Agent **dừng và hỏi user** (theo catalog bên dưới), không đoán, khi:
43
+
44
+ - Gặp API AdsPro không có trong bảng mapping.
45
+ - Placement không có format mapping xác định.
46
+ - Thiếu module provider SuperAds cho network trong config.
47
+ - Callback semantics không thể bảo toàn.
48
+ - Config có placement trùng tên.
49
+ - Không xác định được hành vi của RC key ads.
50
+ - Call site `load()` không xác định được ứng với screen event nào.
51
+ - App chưa có `screenName()` hoặc registry tracking screen.
52
+ - AdItem `NATIVE_COLLAPSIBLE` không đủ cặp layout collapsed + expanded.
53
+ - Không xác định được code cũ nav từ callback show hay dismiss.
54
+ - Click reload: cùng space AdsPro nhưng **khác** layout / unit — không biết tách bao nhiêu target (P2).
55
+ - Flag RC / AB / counter ads không diễn đạt được bằng một JSON tĩnh (P4).
56
+ - Reload cũ không xác định được trigger là click, dismiss hay timer.
57
+ - Wrapper không thể giữ chữ ký cũ (hàm cũ trả về giá trị SuperAds không cung cấp).
@@ -0,0 +1,39 @@
1
+ # Phase 0 — Inventory matrix và checkpoint git
2
+
3
+ ## Checkpoint git
4
+
5
+ Trước Phase 1, tạo checkpoint và **luôn biết mình đang ở checkpoint nào**. Không bắt buộc commit
6
+ tự động — nhưng rollback phải biết điểm trở về.
7
+
8
+ | Checkpoint | Khi nào |
9
+ |---|---|
10
+ | `migration/00-inventory` | Sau khi `migration-inventory.md` đầy đủ |
11
+ | `migration/01-gradle` | Sau Phase 1 |
12
+ | `migration/02-manifest` | Sau Phase 2 |
13
+ | `migration/03-config` | Sau Phase 3 |
14
+ | `migration/04-wrapper` | Sau Phase 4 |
15
+ | `migration/05-call-sites` | Sau Phase 5 |
16
+ | `migration/06-layout` | Sau Phase 6 |
17
+ | `migration/07-cleanup` | Sau Phase 7, trước Global Invariant Check |
18
+
19
+
20
+ ## Inventory
21
+
22
+ Tạo file `migration-inventory.md` trong repo **app** trước khi sửa dòng code nào. Không có file
23
+ này thì không được sang Phase 1.
24
+
25
+ Nguồn: `rg -l "com.android.fullhd.adssdk"`, `rg -n "\.load\("`, mọi `spaceName` trong
26
+ `assets/*.json`, layout native XML + view id, RC key ads.
27
+
28
+ Bảng A-D, Decisions, Inert parameters, Documented exclusions: dùng [templates/migration-inventory.md](../templates/migration-inventory.md).
29
+
30
+ ## Verify Phase 0
31
+
32
+ - [ ] Mọi hàng có `status`
33
+ - [ ] Không hàng nào trống `format`
34
+ - [ ] Số hàng Bảng B = số call site `load()` đếm được
35
+ - [ ] Mọi space reload **có call site** xuất hiện ở Bảng C
36
+ - [ ] Nếu có click reload: Bảng D; layout/unit đích khớp AdsPro lúc click
37
+ - [ ] Đã đọc [migration-pitfalls.md](migration-pitfalls.md) — đo **app này**, không copy số liệu app khác
38
+ - [ ] Nhóm câu hỏi B đã hỏi và ghi vào Decisions
39
+ - [ ] Checkpoint `migration/00-inventory`
@@ -0,0 +1,85 @@
1
+ # Phase 1-2 — Gradle và Manifest
2
+
3
+ ## Phase 1 — Gradle
4
+
5
+ Bỏ:
6
+
7
+ ```gradle
8
+ implementation 'com.android.fullhd.adssdk:AdsPro:...'
9
+ ```
10
+
11
+ Thêm (phiên bản hiện tại của core và mọi plugin: `4.0.0` — `super-ads/build.gradle:6`,
12
+ `MIGRATION.md:7-15`; **đọc lại source / maven hiện tại, không hardcode từ skill nếu source khác**):
13
+
14
+ ```gradle
15
+ def superAds = '4.0.0' // core + MỌI plugin cùng một version (SPI đổi package ở 4.0.0 — trộn 3.x là NoClassDefFoundError lúc chạy)
16
+ implementation "com.android.fullhd.ads:SuperAds:$superAds"
17
+ // Bộ nền: LUÔN khai (kể cả config chưa có unit InHouse)
18
+ implementation "com.android.fullhd.ads:SuperAdsInHouse:$superAds"
19
+ implementation "com.android.fullhd.ads:SuperAdsDebug:$superAds" // có cả ở release
20
+ debugImplementation "com.android.fullhd.ads:SuperAdsDebugOverride:$superAds" // chỉ debug, không lọt release
21
+ implementation "com.android.fullhd.ads:SuperAdsAdmob:$superAds" // BẮT BUỘC nếu config có network Admob (kéo theo SuperAdsUmp)
22
+ implementation "com.android.fullhd.ads:SuperAdsTracking:$superAds" // ad_impression / ad_click Firebase — thiếu là mất doanh thu
23
+ implementation 'com.android.fullhd.layout:SuperAdsLayout:1.0.0-alpha27'
24
+
25
+ // Chỉ thêm khi config có network tương ứng:
26
+ implementation "com.android.fullhd.ads:SuperAdsCloudX:$superAds"
27
+ implementation "com.android.fullhd.ads:SuperAdsApplovin:$superAds"
28
+ ```
29
+
30
+ Wrapper (Phase 4) viết theo API 4.0.0 (vd `com.android.fullhd.super_ads.api.lifecycle.AppLifecycleMonitor`,
31
+ `MIGRATION.md:26`). Pin bản 3.x trở xuống thì wrapper không compile.
32
+
33
+ `SuperAdsTracking` tự cài qua `TrackingInstaller` (ContentProvider), không gọi code. Thiếu module ⇒
34
+ **mất `ad_impression` (doanh thu) và `ad_click`** dù config bật (`MIGRATION.md:452`). Cờ bật/tắt là
35
+ key phẳng trong `global`: `"global": { "tracking.revenue": true }` (mặc định `true`,
36
+ `SA/domain/config/model/AdsConfig.kt:85`).
37
+
38
+ **Thiếu `SuperAdsAdmob` = zero ads im lặng, không crash.** `ConfigRegistryCheck` log
39
+ `ERROR_AND_CRASH` lúc init — chỉ thấy được nếu đã nối logger sink (Phase 4).
40
+
41
+ Giữ nguyên mọi `com.google.ads.mediation:*` hiện có. CloudX adapter (`io.cloudx:adapter-*`) chỉ
42
+ thêm khi user chọn dùng CloudX (nhóm D).
43
+
44
+ Repo SuperAds artifacts: `http://repo.volio.vn/repository/maven-s3/` (`allowInsecureProtocol = true`).
45
+
46
+ Kiểm version **trước khi khai** (mỗi artifact sẽ dùng, `<A>` = `SuperAds`, `SuperAdsAdmob`, `SuperAdsUmp`, `SuperAdsTracking`…):
47
+
48
+ ```bash
49
+ curl -sL https://repo.volio.vn/repository/maven-s3/com/android/fullhd/ads/<A>/maven-metadata.xml | grep -o "<version>[^<]*</version>"
50
+ ```
51
+
52
+ Trạng thái đã kiểm ngày 2026-10-27: repo có `4.0.0` của cả 11 artifact SDK (`SuperAds`, `SuperAdsAdmob`, `SuperAdsAdmobNextGen`, `SuperAdsApplovin`, `SuperAdsArbiter`, `SuperAdsCloudX`, `SuperAdsInHouse`, `SuperAdsTracking`, `SuperAdsUmp`, `SuperAdsDebug`, `SuperAdsDebugOverride`); `SuperAdsContract:1.0.0-alpha` đã có. Trạng thái đổi theo thời gian: luôn chạy lệnh trên, đừng tin con số này. Version cần dùng chưa có trên repo ⇒ Stop, hỏi user: chờ nhóm SDK publish (`scripts/publish-modules.sh --remote`, script đã gồm `ump` và `admob-next-gen`), hay dùng tạm `mavenLocal` / project dependency. Không tự hạ xuống bản cũ: wrapper và config theo API 4.0.0.
53
+
54
+ ### Verify Phase 1
55
+
56
+ - [ ] `./gradlew :app:dependencies --configuration releaseRuntimeClasspath` thấy `SuperAds`,
57
+ `SuperAdsAdmob`, `SuperAdsTracking` cùng `4.0.0`, và **không còn** `AdsPro`
58
+ - [ ] Mediation adapter cũ vẫn còn
59
+ - [ ] Checkpoint `migration/01-gradle`
60
+
61
+ ## Phase 2 — Manifest
62
+
63
+ Giữ:
64
+
65
+ ```xml
66
+ <meta-data
67
+ android:name="com.google.android.gms.ads.APPLICATION_ID"
68
+ android:value="@string/admob_app_id" />
69
+ ```
70
+
71
+ Thêm **chỉ khi** config có network tương ứng:
72
+
73
+ ```xml
74
+ <meta-data android:name="io.cloudx.APP_KEY" android:value="..." />
75
+ <meta-data android:name="applovin.sdk.key" android:value="..." />
76
+ ```
77
+
78
+ Không khai activity / provider ads thủ công — mỗi module SuperAds tự register bằng
79
+ `ContentProvider` (`AdmobInstaller`, `InHouseInstaller`, `CloudXInstaller`, `AppLovinInstaller`).
80
+
81
+ ### Verify Phase 2
82
+
83
+ - [ ] AdMob APPLICATION_ID còn
84
+ - [ ] Key CloudX / AppLovin khớp quyết định nhóm D
85
+ - [ ] Checkpoint `migration/02-manifest`
@@ -0,0 +1,159 @@
1
+ # Phase 3 — Config migration
2
+
3
+ Đọc [config-template.md](config-template.md),
4
+ [api-mapping.md](api-mapping.md) và
5
+ [migration-pitfalls.md](migration-pitfalls.md) **trước** khi viết JSON. Hỏi
6
+ nhóm A và D nếu chưa hỏi.
7
+
8
+ Bảy bảng con 3a–3g + Bảng D. Cập nhật cột `status` của Bảng A/B/C/D khi xong từng hàng.
9
+
10
+ ## 3a — Identity
11
+
12
+ - 1 `spaceName` → 1 `placement` **giữ nguyên tên**.
13
+ - `ids[]` (waterfall high-floor) → nhiều `units` entry, **giữ đúng thứ tự** trong `adItem.units[]`.
14
+ - `adsType` → `format` theo bảng 15 giá trị. Không map được → stop, hỏi nhóm A.
15
+
16
+ ## 3b — Preload: từ `load()` sang event
17
+
18
+ ```text
19
+ BaseFragment.onCreate -> EventScreen.pushEventCreateScreen(screenName())
20
+ BaseFragment.onDestroy -> EventScreen.pushEventDestroyScreen(screenName())
21
+ -> emit "create_{screenName}_{fo|fn}" / "destroy_{screenName}_{fo|fn}"
22
+ -> setUpPreloadTrigger() collect -> Ads.waitAdSDKReady { Ads.dispatchEvent(activity, event) }
23
+ -> khớp "preload.eventActive" của adItem -> preload
24
+ ```
25
+
26
+ SlideShow gọi `pushEventCreateScreen` trong `onCreate`, `pushEventDestroyScreen` trong
27
+ `onDestroy` — không phải `onViewCreated` / `onDestroyView`. Khớp source app đang migrate, không
28
+ đoán lifecycle.
29
+
30
+ - Chuỗi event là **convention của app**, không phải hằng số SDK. JSON khớp **từng ký tự**; lệch
31
+ một ký tự là **im lặng không preload**, không log lỗi.
32
+ - `_fo` / `_fn` (first-open / quay lại) là **hai chuỗi khác nhau**. Code cũ preload vô điều kiện
33
+ thì khai **cả hai**, như SlideShow: `["create_open_screen_5_fo", "create_open_screen_5_fn"]`.
34
+ - Chỉ màn **đã hook** `pushEventCreateScreen` mới emit (Activity thường sót). `isFo` nếu
35
+ `lazy` thì lock lần đầu. Event = màn **đang đứng** lúc `load()`, không đoán màn kế.
36
+ - Key thuộc tầng **`adItems`**; đặt lên `placements` bị bỏ qua.
37
+ - `preload.enabled` default **true** (`AdItemConfig.isPreloadEnabled()`). Lật false trên adItem
38
+ `SHOW_LOADED` giết âm thầm placement vì cache luôn rỗng.
39
+ - Key phụ: `preload.max`, `preload.delayLoadFistMs` (đúng chính tả source — `Fist` không phải
40
+ `First`), `preload.delayLoadAfterShowMs`, `preload.autoPreloadAfterDismiss`.
41
+ - Mỗi hàng Bảng B → một chuỗi thêm vào `preload.eventActive` của adItem tương ứng.
42
+ - **Không** thay `load()` bằng `Ads.preload.preloadNow` (API thủ công, đã `@Deprecated` —
43
+ `SA/api/AdsFeatures.kt:90`).
44
+
45
+ Phase 3b và Phase 4 khớp nhau **qua chuỗi event** — coupling duy nhất được phép.
46
+
47
+ ## 3c — Layout
48
+
49
+ - Native / banner thường: **một** key `ui.layoutAdName.{network}`.
50
+ - Collapsible (`NATIVE_COLLAPSIBLE`): **hai** key —
51
+ `ui.layoutAdName.{network}` = **collapsed / small**,
52
+ `ui.layoutAdNameExpand.{network}` = **expanded / large**.
53
+ - Từ AdsPro: `AdmobNativeCollapsible.show(..., layoutRes, collapsibleLayoutRes, ...)` →
54
+ `layoutRes` vào key thường, `collapsibleLayoutRes` vào key Expand.
55
+ **Đảo hai cái thì ad vẫn fill, vẫn có impression, build vẫn pass, chỉ hiện sai cỡ.**
56
+ - Suffix network **chữ thường, phân biệt hoa thường**: `.admob`, `.cloudx`, `.applovin`, `.meta`.
57
+ AdItem trộn network phải khai key cho **từng** network, thiếu là `RenderError.LayoutNotFound`.
58
+ - Giá trị là **tên file XML không đuôi**, resolve trong res app hoặc `SuperAdsLayout`.
59
+ - Loading: `ui.loading.layoutName` + `ui.loading.enable`.
60
+ - `NativeConfig(resNativeMeta)` (layout riêng khi mediation AdMob trả về Meta) → **loại B**:
61
+ `ui.layoutAdName.meta` (+ `ui.layoutAdNameExpand.meta` cho collapsible). Plugin AdMob chọn layout
62
+ meta khi response là Meta mediation và file XML tồn tại, ngược lại dùng layout thường
63
+ (`SA/presentation/render/NativeRenderer.kt:97`, `SA/presentation/render/CollapsibleNativeRenderer.kt:80`;
64
+ `super-ads-admob/src/main/java/com/android/fullhd/super_ads/plugin/admob/nativead/NativeLayoutResolver.kt:40`). `meta` chỉ là **hậu tố key**, không phải
65
+ network — không khai unit `"network": "Meta"` (`SA/domain/config/key/AdConfigKey.kt:42`).
66
+ - **Một** `ui.layoutAdName` / adItem / network. AdsPro xoay `index % N` layout → P5, hỏi product.
67
+ - Native full chain: XML app **hoặc** catalog SuperAdsLayout — product chọn, ghi Decisions. Không tự coi lệch XML là bug, cũng không mặc định catalog.
68
+
69
+ ## 3d — Chuyển màn: theo behavior thật của app
70
+
71
+ - `mode.show`: `LOAD_AND_SHOW` (default) ứng `loadAndShow(...)`; `SHOW_LOADED` ứng `show(...)`
72
+ **có gate `getStatusBySpace == LOADED`**. Vì vậy `getStatusBySpace` làm gate là loại **B**.
73
+ - `mode.navigate`: chỉ `NAVIGATE_ON_SHOW` / `NAVIGATE_ON_DISMISS`. Default
74
+ `NAVIGATE_ON_DISMISS` (`AdItemConfig.getModeNavigate()`; giá trị lạ bị ép về default).
75
+ Xác định **bắt buộc đọc từ app**: nav trong `onAdShowedFullScreenContent` → `NAVIGATE_ON_SHOW`;
76
+ trong `onAdDismissedFullScreenContent` → `NAVIGATE_ON_DISMISS`. Không suy ra được thì hỏi nhóm B.
77
+ - Sau migration app **vẫn sở hữu nav đi đâu** qua `NavAdCallback`, chỉ không còn sở hữu *thời điểm*.
78
+ - Không áp một `mode.navigate` chung. Splash / leftover / in-app / chain đo riêng (P1).
79
+ - Placement chain: một giá trị không cover hai path AdsPro (nav lúc show vs lúc đóng native). Ghi Decisions nếu lệch.
80
+
81
+ ## 3e — Reload: đưa hết vào `placements`
82
+
83
+ SDK tự chạy — `ReloadCoordinator.Plan.from(placement, surface)` trong core đọc config. App
84
+ **không viết một dòng reload nào**. Wrapper **không** có hàm reload. SlideShow từng viết
85
+ `showAdViewWithReload` rồi comment bỏ toàn bộ — đó là bằng chứng, không phải code cần copy.
86
+
87
+ | Trigger | Key | Timing kèm theo |
88
+ |---|---|---|
89
+ | click vào ad | `reload.click.placementID` | - |
90
+ | đóng ad (collapsible dismiss) | `reload.dismiss.placementID` | `show.timeDelayShowAfterDismissMs` |
91
+ | chu kỳ tự động | `reload.auto.placementID` | `show.timeAutoReloadMs` |
92
+
93
+ Mẫu chuẩn: cặp placement sinh đôi `X` / `X_Reload`, khớp họ space `*_Reload` của app cũ.
94
+
95
+ Layout + unit thuộc **đích**. Gộp / tách theo Bảng D của **app này** (P2) — không hardcode một twin hay N twin. Click lại cùng space AdsPro → self-click trên twin. Chain: `reload.click` trên placement chain.
96
+
97
+ `ConfigRegistryCheck` log issue lúc init (key vẫn bị bỏ qua, không crash):
98
+
99
+ - `show.timeAutoReloadMs` mà **thiếu** `reload.auto.placementID` ⇒ auto-reload **không chạy**.
100
+ - Placement gốc INTER / REWARD / APP_OPEN mang `reload.*` ⇒ bỏ qua (không có container để thay tại
101
+ chỗ, `SA/config/validate/ConfigRegistryCheck.kt:182`).
102
+ - Placement đích không tồn tại / tắt / priority rỗng / **khác loại bề mặt** với gốc ⇒ bỏ qua
103
+ (`SA/config/validate/ConfigRegistryCheck.kt:202`).
104
+ - Placement **chain** không nhận `reload.auto` (tự advance slot, timer tranh render).
105
+ - Placement **chain** không nhận `reload.dismiss` (đóng slot = sang bước kế). Chain chỉ reload bằng click.
106
+
107
+ Ngoài `ConfigRegistryCheck`:
108
+
109
+ - Chu kỳ auto bị `ReloadCoordinator` kẹp sàn `MIN_AUTO_RELOAD_MS = 5_000L`
110
+ (`SA/show/ReloadCoordinator.kt:23`, `SA/show/ReloadCoordinator.kt:82`).
111
+ - Key `reload.placementID` (thiếu `.click` / `.dismiss` / `.auto`) **không tồn tại** trong
112
+ `AdConfigKey.Reload` — có trong config demo nhưng **vô hiệu**, SDK không báo gì. Không được dùng.
113
+
114
+ ## 3f — Analytics: tên event vào JSON
115
+
116
+ | Key (tầng `adItems`) | Bắn khi | Tương ứng AdsPro |
117
+ |---|---|---|
118
+ | `tracking.eventImpress` | impression | `onAdImpression` + `logPairValue*` |
119
+ | `tracking.eventLoaded` | load xong | `onAdLoaded` |
120
+ | `tracking.eventPaid` | paid event | `onAdPaidValueListener` |
121
+ | `tracking.mode` | `ALWAYS` / `ONCE_PER_PROCESS` / `ONCE_PER_LIFETIME` | `Set` chống trùng tự viết |
122
+
123
+ Giá trị là **mảng** tên event. Event bắn ra `TrackerEvent.TrackingEvent` trên `Ads.tracker.events`;
124
+ app forward sang Firebase / Meta ở **một** chỗ trong wrapper.
125
+
126
+ Doanh thu tự động: artifact `com.android.fullhd.ads:SuperAdsTracking` (Phase 1; tự cài khi có
127
+ dependency, không gọi code; bật/tắt bằng key phẳng `tracking.enable` / `tracking.revenue` /
128
+ `tracking.perf` / `tracking.arbiter` trong object `global`) và
129
+ `Ads.tracking.enableAppsflyer(app, appsflyerId)` — thay `pushAppsflyerRevenue` thủ công.
130
+
131
+ Event JSON **không mang** được cặp `previous_screen` / `next_screen`. Hỏi nhóm B.
132
+
133
+ `global.version` là nhãn để soi config nào đang chạy (Toast `UpdateJson`). Bump version mỗi lần
134
+ sửa JSON để Toast chứng minh file đang chạy (O1).
135
+
136
+ ## 3g — Native sau inter (`INTERSTITIAL_NATIVE_CHAIN`)
137
+
138
+ Chỉ khi **app này** có dialog / flow native sau inter. Công thức SDK:
139
+ `nextPos >= firstShowAt && (nextPos - firstShowAt) % showEvery == 0`.
140
+ Điền số từ counter AdsPro của app — template `1`/`2`/`5000` / 1–2 native **không** phải chuẩn sản phẩm.
141
+
142
+ - `adItems.native[]` = số native production (kèm RC/AB **tên key của app này**), không = số space JSON.
143
+ - Countdown = timer dialog app này.
144
+ - `reload.click` trên placement chain. Xoá dialog Kotlin song song (Phase 5b).
145
+ - adItem chain `SHOW_LOADED` cần cache: `preload.enabled` (mặc định true) **không** được tắt và phải
146
+ có `preload.eventActive`. Tắt preload thì không preload, không vào event map, nhánh `SHOW_LOADED`
147
+ luôn `ModeShowedButNotHaveAdCached` ⇒ placement không bao giờ show
148
+ (`SA/engine/load/Preloader.kt:225`, `SA/config/repository/AdsConfigRepositoryImpl.kt:180`, `SA/engine/load/PriorityLoader.kt:163`).
149
+ Không preload được thì dùng `LOAD_AND_SHOW`.
150
+
151
+ `forceLoadNewAdIfShowed` **không** suy ra `mode.show`. Đọc `show` vs `loadAndShow` + gate `LOADED`.
152
+
153
+ ## Verify Phase 3
154
+
155
+ - [ ] Checklist referential integrity trong `config-template.md` pass
156
+ - [ ] Cột `status` Bảng A/B/C cập nhật; nếu có click reload thì Bảng D layout/unit khớp AdsPro
157
+ - [ ] `Ads.init` / RC khớp file JSON vừa viết (P0)
158
+ - [ ] Nếu có chain: `adItems.native` + gate khớp đo Phase 0 của **app này**
159
+ - [ ] Checkpoint `migration/03-config`
@@ -0,0 +1,70 @@
1
+ # Phase 4 — Wrapper, EventScreen, observability
2
+
3
+ Hỏi nhóm C trước. Mặc định khuyến nghị: **moi ruột file `AdsUtils.kt` cũ**, giữ path, giữ chữ ký
4
+ mọi hàm public, thay ruột bằng delegate xuống `Ads.show`.
5
+
6
+ Đọc [wrapper-template.md](wrapper-template.md).
7
+
8
+ Bề mặt cần có: `init / setPremium / isPremium / showCmp / setUpAppOpen / setUpPreloadTrigger /
9
+ dispatcherEvent` + 4 extension `showAdView`, `showAdFull`, `showAdFullInApp`, `showAdRewardFull`
10
+ (extension có thể private nội bộ file nếu màn hình vẫn gọi chữ ký cũ).
11
+
12
+ `EventScreen`: `MutableSharedFlow` replay 0, extraBufferCapacity 64. Hook base fragment:
13
+ `onCreate` → `pushEventCreateScreen(screenName())`, `onDestroy` →
14
+ `pushEventDestroyScreen(screenName())`. Collector của `EventScreen` phải subscribe **trước** màn đầu
15
+ emit (replay 0 ⇒ emit khi chưa có subscriber là mất): launch bằng `Dispatchers.Main.immediate`.
16
+
17
+ **Consent trước preload / app open.** Core không tự chặn request khi chưa có consent
18
+ (`MIGRATION.md:126-127`). Wrapper giữ cờ `consentDone`: `showCmp` set cờ trong callback; preload
19
+ trigger chỉ `Ads.dispatchEvent` khi cờ bật (event màn đầu nhận trong lúc chờ CMP được giữ lại, phát
20
+ sau callback); `setUpAppOpen` bỏ qua khi cờ tắt. Xem wrapper-template.
21
+
22
+ Ba mối observability — **bắt buộc trước khi verify**, không phải tuỳ chọn:
23
+
24
+ **(a) Logger sink** — `Ads.logger.init(LogConfig(enabled = true, minLevel = LogLevel.FLOW),
25
+ debug = isDebug, externalSink)` (`LogLevel` không có `VERBOSE`; `FLOW` là mức thấp nhất,
26
+ `SA/domain/model/log/LogLevel.kt:6`). Logcat chỉ ở debug; `ERROR` / `ERROR_AND_CRASH` thì debug Toast (post lên
27
+ main — sink chạy đồng bộ trên luồng đang log, thường là IO; Toast ngoài main thread ném exception và
28
+ bị SDK nuốt trong `runCatching`, `SA/infra/logger/SdkAdLogger.kt:50`, nên Toast không hiện), release
29
+ `Firebase.crashlytics.recordException(Exception(event.toJsonString()))`. Đây là đường duy nhất
30
+ thấy issue `ConfigRegistryCheck`.
31
+
32
+ **(b) Tracker collector** — collect `Ads.tracker.events` một lần trên `Dispatchers.Main`:
33
+
34
+ | Event | Việc làm |
35
+ |---|---|
36
+ | `TrackingEvent` | forward `event.eventName` sang Firebase; thêm hậu tố `_oke` / `_fail` theo `FirebaseRemoteConfig.getValue(key).source == VALUE_SOURCE_REMOTE` |
37
+ | `StartRequest` / `Impression` | event bespoke theo `event.data.adItemId` (`logOnce` cho impression) |
38
+ | `UpdateJson` | DEBUG: Toast `"Load version ${event.version}"` |
39
+ | `Click` | DEBUG: Toast `unitId.takeLast(4)` + `adItemId` |
40
+ | `FailedToLoad` / `FailedToShowFull` | DEBUG: Toast kèm `error.code` |
41
+
42
+ `global.tracking.item_events` mặc định bật (không khai = `true`, từ 3.0.6) ⇒ `SuperAdsTracking` tự forward `TrackingEvent` (kèm `ab_variant`): **bỏ** dòng forward `TrackingEvent` ở trên (hoặc khai `false`), nếu không event bị gửi đôi. Dự án chạy Firebase A/B bằng RC: nạp lại config bằng `Ads.loadFromJson(json, firebaseActivated = true)` **chỉ** sau `activate()` (`loadFromJson(json)` và config provider không push activation event) — xem `MIGRATION.md`, mục A/B test bằng remote config.
43
+
44
+ **(c) Config provider** — overload `Ads.init(application, configProvider = { ... }, isDebug)`:
45
+
46
+ ```text
47
+ assets = readAssetString(file, timeout 2000)
48
+ remote = getRemoteConfigString(key, fallbackToOldValue = true, timeout 3500)
49
+ remote hợp lệ (parse được JSON object có "placements") ? remote : assets
50
+ ```
51
+
52
+ JSON RC hỏng mà vẫn trả về ⇒ SDK parse ra `null` ⇒ init **FAILED**, mất sạch ad, không có bản dự
53
+ phòng (`SA/runtime/SdkRuntime.kt:226`, `SA/runtime/SdkRuntime.kt:275`). Vì vậy provider phải kiểm
54
+ JSON trước khi trả. Muốn có bản dự phòng: `Ads.init(application, fileNameAsset)` chạy asset ngay, rồi nạp
55
+ giá trị RC sau `fetchAndActivate()` bằng `Ads.loadFromJson(json, firebaseActivated = true)` (`SA/api/Ads.kt:197`);
56
+ JSON hỏng bị từ chối, giữ bản đang chạy — hỏi nhóm C. (`initRemote` mới mở lại, chỉ app mẫu dùng; chưa kiểm trên thiết bị.)
57
+
58
+ `minimumFetchIntervalInSeconds = 0` như SlideShow là rất hung — hỏi nhóm D. RC key phụ
59
+ `keyTimeBetweenAdInter` không có tương đương.
60
+
61
+ Cấm thêm vào wrapper: state timing, cache riêng, retry, adapter callback SDK, preload thủ công,
62
+ driver reload, tách file helper theo loại ad.
63
+
64
+ ## Verify Phase 4
65
+
66
+ - [ ] Chữ ký public của wrapper cũ không đổi
67
+ - [ ] EventScreen emit chuỗi khớp `preload.eventActive` đã viết ở Phase 3b
68
+ - [ ] Ba mối observability đã nối
69
+ - [ ] Không event preload / app open nào chạy trước callback `showCmp`
70
+ - [ ] Checkpoint `migration/04-wrapper`
@@ -0,0 +1,73 @@
1
+ # Phase 5-7 — Call sites, Layout, Cleanup
2
+
3
+ # Phase 5 — Call sites, chia hai nhóm
4
+
5
+ ## 5a — Không sửa màn hình
6
+
7
+ Mọi lời gọi mà wrapper giữ được chữ ký thì **màn hình không đổi một ký tự**:
8
+ `showAdsNative(...)`, `showAdsInterGeneral(...)`, `showNativeCollapsible(...)`,
9
+ `showInterAdsSplash(...)`, `showInterSplashLate(...)`.
10
+
11
+ ## 5b — Danh sách đóng: chỉ những lý do này mới được sửa màn hình
12
+
13
+ | Lý do | Hành động |
14
+ |---|---|
15
+ | `AdmobX.load(space)` gọi trực tiếp trong màn | Xoá call site (đã ở `preload.eventActive`) |
16
+ | `getStatusBySpace(...) == LOADED` làm gate trước show | Xoá gate (đã ở `mode.show: SHOW_LOADED`) |
17
+ | Gọi lại `showAdsNative("X_Reload", ...)` trong `onAdClicked` | Xoá (đã ở `reload.click.placementID`) |
18
+ | Timer reload trong Fragment | Xoá (đã ở `reload.auto.placementID` + `show.timeAutoReloadMs`) |
19
+ | `AdmobOpenResume.loadAndAutoShowIfAvailable(...)` rải rác | Xoá, dồn về `setUpAppOpen(...)` một lần trong init |
20
+ | Dialog native-sau-inter tự viết | Xoá, dùng `INTERSTITIAL_NATIVE_CHAIN` + `chain.native.showEvery` |
21
+ | Pseudo-reward bằng native dialog | Đổi sang placement `REWARD` / `NATIVE_FULL_ACTIVITY` thật |
22
+ | Màn gọi trực tiếp `Admob*` không qua wrapper | Đổi sang chữ ký wrapper |
23
+ | Base fragment | Thêm hook `EventScreen` |
24
+ | Override `onAdPaidValueListener` / `logPairValue*` trong màn hoặc dialog | Xoá, gom về tracker collector (tên event ở `tracking.event*`) |
25
+
26
+ Không có lý do trong bảng này thì **không sửa file đó**.
27
+
28
+ ## Verify Phase 5
29
+
30
+ - [ ] `rg "com.android.fullhd.adssdk"` rỗng
31
+ - [ ] Không còn `.load(` trên code ads
32
+ - [ ] Không còn lời gọi tay tới placement `*_Reload`
33
+ - [ ] Mọi file màn hình bị sửa truy được về đúng một dòng bảng 5b
34
+ - [ ] Ba bảng inventory `status = DONE`
35
+ - [ ] Checkpoint `migration/05-call-sites`
36
+
37
+
38
+
39
+ # Phase 6 — Layout
40
+
41
+ Layout AdMob chuẩn giữ nguyên. Sửa tay:
42
+
43
+ - Id badge `ivAdMark` (AdsPro) → `tvAdMark` (SuperAds `ProtectedAssets`).
44
+ - `iv_blur` chỉ còn ý nghĩa với AdMob.
45
+
46
+ Xoá `R.layout.*` khỏi **thân wrapper** (call site vẫn truyền vào — tham số inert). Đối chiếu
47
+ ngược: mỗi giá trị `ui.layoutAdName*` có file XML tồn tại thật (app res hoặc SuperAdsLayout).
48
+
49
+ View id bắt buộc: `ad_media`, `ad_app_icon`, `ad_headline`, `ad_body`, `ad_call_to_action`,
50
+ `ad_choices_container`, `tvAdMark`. Optional: `ad_price`, `ad_store`, `ad_stars`, `ad_advertiser`.
51
+
52
+ ## Verify Phase 6
53
+
54
+ - [ ] Không layout collapsible nào đảo collapsed / expanded
55
+ - [ ] Mọi `ui.layoutAdName*` resolve được
56
+ - [ ] Checkpoint `migration/06-layout`
57
+
58
+
59
+
60
+ # Phase 7 — Cleanup
61
+
62
+ Xoá dialog ads tự viết và timer đã bị JSON thay thế. Xoá import chết.
63
+
64
+ **Không** xoá file wrapper. **Không** dọn tham số inert (commit riêng).
65
+
66
+ Cách SlideShow comment lại 650 dòng `AdsUtils.kt` là điều **không** nên copy — hoặc moi ruột,
67
+ hoặc xoá, không để code chết.
68
+
69
+ ## Verify Phase 7
70
+
71
+ - [ ] Không còn dialog ads tự viết đã bị `INTERSTITIAL_NATIVE_CHAIN` thay
72
+ - [ ] Không còn file utils ads mới ngoài quyết định nhóm C
73
+ - [ ] Checkpoint `migration/07-cleanup`
@@ -0,0 +1,73 @@
1
+ # Authority Order
2
+
3
+ Khi các nguồn xung đột, thứ tự thẩm quyền:
4
+
5
+ 1. Source SuperAds hiện tại (`super-ads/src/main/.../api/`, `domain/config/key/AdConfigKey.kt`, `core/flow/`)
6
+ 2. Migration production của SlideShow — **cho API và JSON**, không cho chiến lược diff
7
+ 3. Source AdsPro
8
+ 4. Template / example trong skill này
9
+ 5. Suy luận của agent
10
+
11
+ Không dùng example/template trong skill để suy ra API nếu source thực tế khác. **Không được tự
12
+ suy luận nếu source chưa chứng minh.** Không copy `showEvery`, số native chain, countdown, RC
13
+ key, hay `mode.navigate` từ SlideShow / app đã migrate khác — đo app hiện tại.
14
+
15
+ # Ba nguyên tắc
16
+
17
+ Thứ tự khi xung đột: **JSON-first > wrapper-first > minimal-diff**. Giữ diff nhỏ không bao giờ
18
+ là lý do để giữ behavior trong Kotlin.
19
+
20
+ # 1. JSON-first
21
+
22
+ Một dòng behavior mà JSON diễn đạt được thì **không được tồn tại trong Kotlin**. Ba ví dụ điển
23
+ hình (bảng đầy đủ behavior → key JSON, theo từng tầng `adItems`/`placements`/`wrapper`: xem
24
+ [config-template.md](config-template.md) §"Key theo tầng" và
25
+ [api-mapping.md](api-mapping.md)):
26
+
27
+ | Behavior | Kotlin cũ (AdsPro) | JSON mới |
28
+ |---|---|---|
29
+ | preload theo thời điểm | `AdmobX.load(space)` rải rác | `preload.eventActive` |
30
+ | gate "chỉ show khi đã load" | `getStatusBySpace == LOADED` | `mode.show: SHOW_LOADED` |
31
+ | auto reload chu kỳ | timer trong Fragment | `reload.auto.placementID` + `show.timeAutoReloadMs` |
32
+
33
+ Code smell sau migration — còn thấy là chưa xong: `Handler`/`delay` quanh ads, biến `countShowAd`,
34
+ `System.currentTimeMillis()` so khoảng cách ad, `Ads.preload*` thủ công, `if (status == LOADED)`
35
+ trước show, `R.layout.*` trong **thân wrapper**, timer reload trong Fragment.
36
+
37
+ # 2. Wrapper-first
38
+
39
+ **Giữ vỏ, bỏ ruột.** Hàm gọi ad gốc của app được giữ **nguyên chữ ký** và trở thành lớp wrapper
40
+ mỏng delegate xuống `Ads.show`. Behavior bên trong bị bỏ hết vì đã sang JSON. Màn hình **không bị
41
+ sửa** nếu chữ ký giữ được.
42
+
43
+ Giữ chữ ký ≠ port behavior. Điều bị cấm là mang *logic* cũ sang, không phải mang *tên hàm* sang.
44
+
45
+ Một số tham số trở thành **inert** (`idLayoutAds`, `layoutLoadingRes`, `forceLoadNew`, …). Rủi
46
+ ro: sau này có người đổi `R.layout.x` ở call site và không có gì xảy ra. Xử lý:
47
+
48
+ - Mỗi tham số inert ghi vào bảng "Inert parameters" trong `migration-inventory.md`.
49
+ - Trong thân wrapper có **một** dòng comment nêu tham số đó giờ do JSON quyết định.
50
+ - Dọn tham số inert là **commit riêng sau migration**, không nằm trong phạm vi này.
51
+
52
+ # 3. Minimal-diff
53
+
54
+ - Không tạo file utils mới nếu đã có nhà. File `AdsUtils.kt` (hoặc tương đương) cũ được **giữ lại
55
+ làm nhà cho wrapper** — moi ruột, giữ path và tên. Giữ path nghĩa là ~40 màn hình không đổi
56
+ một dòng `import`.
57
+ - Một file wrapper duy nhất. Không tách `NativeAdsHelper` / `InterAdsHelper`.
58
+ - Không sửa màn hình nếu wrapper giữ được chữ ký. Mỗi lần sửa màn hình phải có lý do thuộc đúng
59
+ danh sách Phase 5b.
60
+ - Code mới (`EventScreen`, hook base fragment) ưu tiên thêm vào file đã có trong package base.
61
+ - Mục tiêu diff: `≈ 1 file config JSON + 1 file wrapper + 1 file base + n màn hình có lý do 5b`.
62
+ Không phải 40 màn hình.
63
+
64
+ SlideShow (commit `4e36e791`) tạo `AdsSP.kt` mới và comment bỏ 650 dòng `AdsUtils.kt`, sửa ~158
65
+ file. Skill dùng SlideShow làm authority cho **API và JSON**, nhưng **không** copy chiến lược
66
+ diff của nó.
67
+
68
+ ```text
69
+ AdsPro: 40 màn --space + R.layout + timing--> AdsUtils (650 dòng) --> Admob*.load/show
70
+ SuperAds: 40 màn KHÔNG ĐỔI --chữ ký cũ--> AdsUtils moi ruột --> Ads.show
71
+ EventScreen --> Ads.dispatchEvent
72
+ JSON (format, layout, mode, preload, reload, chain)
73
+ ```