@maccesar/titools 5.1.1 → 5.2.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.
- package/README.md +44 -12
- package/agents/ti-pro.md +4 -0
- package/commands/ti-audit.md +4 -0
- package/lib/config.js +2 -0
- package/package.json +1 -1
- package/skills/ti-admob-unified/SKILL.md +44 -0
- package/skills/ti-admob-unified/agents/openai.yaml +4 -0
- package/skills/ti-admob-unified/assets/example/app.js +1003 -0
- package/skills/ti-admob-unified/assets/example/lib/ads.js +618 -0
- package/skills/ti-admob-unified/assets/example/library-usage.js +104 -0
- package/skills/ti-admob-unified/references/api-reference.md +592 -0
- package/skills/ti-admob-unified/references/banners.md +435 -0
- package/skills/ti-admob-unified/references/contributing.md +228 -0
- package/skills/ti-admob-unified/references/dependencies.md +85 -0
- package/skills/ti-admob-unified/references/fullscreen.md +223 -0
- package/skills/ti-admob-unified/references/game-monetization.md +17 -0
- package/skills/ti-admob-unified/references/getting-started.md +275 -0
- package/skills/ti-admob-unified/references/index.md +30 -0
- package/skills/ti-admob-unified/references/integration.md +85 -0
- package/skills/ti-admob-unified/references/library.md +189 -0
- package/skills/ti-admob-unified/references/mediation.md +132 -0
- package/skills/ti-admob-unified/references/migration.md +255 -0
- package/skills/ti-admob-unified/references/module-changelog.md +54 -0
- package/skills/ti-admob-unified/references/module-license.txt +21 -0
- package/skills/ti-admob-unified/references/privacy.md +254 -0
- package/skills/ti-admob-unified/references/roadmap.md +146 -0
- package/skills/ti-admob-unified/references/source-notes.md +33 -0
- package/skills/ti-admob-unified/references/source-snapshot.json +440 -0
- package/skills/ti-admob-unified/references/third-party-notices.md +22 -0
- package/skills/ti-game/SKILL.md +10 -1
- package/skills/ti-game/references/api.md +10 -2
- package/skills/ti-game/references/debugging-performance.md +6 -0
- package/skills/ti-game/references/examples.md +11 -1
- package/skills/ti-game/references/patterns.md +24 -2
- package/skills/ti-game/references/physics.md +502 -0
- package/skills/ti-game/references/project-setup.md +23 -1
- package/skills/ti-game/references/recipes-action.md +46 -14
- package/skills/ti-game/references/recipes-adventure-effects.md +12 -1
- package/skills/ti-game/references/roadmap.md +2 -2
- package/skills/ti-game/references/tilemaps.md +2 -1
- package/skills/ti-game-assets/SKILL.md +53 -0
- package/skills/ti-game-assets/agents/openai.yaml +4 -0
- package/skills/ti-game-assets/references/alpha.md +26 -0
- package/skills/ti-game-assets/references/art-direction.md +37 -0
- package/skills/ti-game-assets/references/integration.md +49 -0
- package/skills/ti-game-assets/references/manifest.example.json +429 -0
- package/skills/ti-game-assets/references/manifest.md +39 -0
- package/skills/ti-game-assets/references/offline-audio.md +33 -0
- package/skills/ti-game-assets/references/prompts.md +33 -0
- package/skills/ti-game-assets/references/repeating-textures.md +17 -0
- package/skills/ti-game-assets/references/responsible-ads.md +31 -0
- package/skills/ti-game-assets/references/sound-assets.md +42 -0
- package/skills/ti-game-assets/references/vector-sources.md +21 -0
- package/skills/ti-game-assets/scripts/alpha_tools.py +160 -0
- package/skills/ti-game-assets/scripts/requirements.txt +1 -0
- package/skills/ti-game-assets/scripts/validate_manifest.py +267 -0
- package/skills/ti-synthengine/SKILL.md +37 -13
- package/skills/ti-synthengine/references/api.md +397 -31
- package/skills/ti-synthengine/references/audio-engine.md +235 -14
- package/skills/ti-synthengine/references/examples.md +396 -17
- package/skills/ti-synthengine/references/guide.md +331 -12
- package/skills/ti-synthengine/references/recipes.md +212 -22
- package/skills/ti-synthengine/references/recommendations.md +53 -16
- package/skills/ti-synthengine/references/sound-design.md +359 -15
- package/skills/ti-synthengine/references/source-notes.md +121 -0
- package/skills/ti-synthengine/references/source-snapshot.json +105 -0
- package/skills/ti-synthengine/references/stats-performance.json +2992 -0
- package/skills/ti-synthengine/references/stats-performance.md +38 -0
- package/skills/ti-synthengine/references/troubleshooting.md +61 -4
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
> Source snapshot: official ti.admob.unified 1.0.0 documentation at commit `5b4cbf9` (copied 2026-10-11). Relative links rebased for this skill.
|
|
2
|
+
|
|
3
|
+
# Dependency inventory
|
|
4
|
+
|
|
5
|
+
The core and five optional complements use the published versions verified on September 23, 2026. Each module has its own iOS Podfile, lockfile and framework checksum inventory. Android pins live in each module’s `android/build.gradle`.
|
|
6
|
+
|
|
7
|
+
## iOS vendored XCFrameworks: `ti.admob.unified`
|
|
8
|
+
|
|
9
|
+
| Component | Framework | Version |
|
|
10
|
+
| ----------------------- | ----------------------------------- | -------: |
|
|
11
|
+
| Google Mobile Ads | `GoogleMobileAds.xcframework` | `13.10.0` |
|
|
12
|
+
| User Messaging Platform | `UserMessagingPlatform.xcframework` | `3.1.0` |
|
|
13
|
+
|
|
14
|
+
## iOS per-network complements
|
|
15
|
+
|
|
16
|
+
The eleven frameworks are stored without duplication under `mediation/<network>/ios/platform/`: AppLovin (2), Meta (2), InMobi (2), Unity (3), Liftoff (2). Unity Ads 4.20.1 no longer requires IronSourceAdQualitySDK. No ironSource advertising adapter or SDK is distributed.
|
|
17
|
+
|
|
18
|
+
| Component | Framework | Version |
|
|
19
|
+
| ------------------------- | ------------------------------------ | -----------: |
|
|
20
|
+
| AppLovin adapter | `AppLovinAdapter.xcframework` | `13.6.4.0` |
|
|
21
|
+
| AppLovin SDK | `AppLovinSDK.xcframework` | `13.6.4` |
|
|
22
|
+
| Meta adapter | `MetaAdapter.xcframework` | `6.22.0.0` |
|
|
23
|
+
| Meta Audience Network SDK | `FBAudienceNetwork.xcframework` | `6.22.0` |
|
|
24
|
+
| InMobi adapter | `InMobiAdapter.xcframework` | `11.4.1.0` |
|
|
25
|
+
| InMobi SDK | `InMobiSDK.xcframework` | `11.4.1` |
|
|
26
|
+
| Unity adapter | `UnityAdapter.xcframework` | `4.20.1.0` |
|
|
27
|
+
| Unity Ads SDK | `UnityAds.xcframework` | `4.20.1` |
|
|
28
|
+
| Unity support library | `UnityCoherenceLib.xcframework` | `0.1.0` |
|
|
29
|
+
| Liftoff Monetize adapter | `LiftoffMonetizeAdapter.xcframework` | `7.7.7.0` |
|
|
30
|
+
| Vungle Ads SDK | `VungleAdsSDK.xcframework` | `7.7.7` |
|
|
31
|
+
|
|
32
|
+
The version column is the pinned CocoaPods version; lockfiles record the resolved adapter and SDK pair. Meta now uses SDK 6.22.0 on both platforms. Liftoff uses SDK 7.7.7 on iOS and 7.7.8 on Android. The version contract test retains this remaining difference and checks the matching networks.
|
|
33
|
+
|
|
34
|
+
The official CocoaPods specifications resolve the adapter and matching network SDK pairs. Each module has its own `ios/install_frameworks.sh`: it downloads that module's exact Podfile versions, stages its framework set, verifies its tree hashes, and restores the previous platform directory if validation fails. Run `ios/install_frameworks.sh` for the two core frameworks and `mediation/<network>/ios/install_frameworks.sh` for one complement. Complement installers use `pod install --deployment` to preserve the locked resolution.
|
|
35
|
+
|
|
36
|
+
The tree checksum is calculated from the sorted list of relative file paths and the SHA-256 of every file in the XCFramework:
|
|
37
|
+
|
|
38
|
+
```sh
|
|
39
|
+
find . -type f -print0 | LC_ALL=C sort -z | xargs -0 shasum -a 256 |
|
|
40
|
+
shasum -a 256
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## Android Maven dependencies: `ti.admob.unified`
|
|
44
|
+
|
|
45
|
+
Declared in `android/build.gradle`.
|
|
46
|
+
|
|
47
|
+
| Coordinate | Version |
|
|
48
|
+
| ------------------------------------------------ | ------: |
|
|
49
|
+
| `com.google.android.gms:play-services-ads` | 25.5.0 |
|
|
50
|
+
| `com.google.android.ump:user-messaging-platform` | 4.0.0 |
|
|
51
|
+
| `com.google.guava:guava` | 33.7.1-android |
|
|
52
|
+
|
|
53
|
+
## Android per-network complements
|
|
54
|
+
|
|
55
|
+
Declared separately in `mediation/<network>/android/build.gradle`; `meta` uses the `facebook` coordinate and `liftoff` uses `vungle`.
|
|
56
|
+
|
|
57
|
+
| Coordinate | Version |
|
|
58
|
+
| ----------------------------------- | -------: |
|
|
59
|
+
| `com.google.ads.mediation:facebook` | 6.22.0.1 |
|
|
60
|
+
| `com.google.ads.mediation:inmobi` | 11.4.1.2 |
|
|
61
|
+
| `com.google.ads.mediation:unity` | 4.20.1.0 |
|
|
62
|
+
| `com.google.ads.mediation:applovin` | 13.6.4.2 |
|
|
63
|
+
| `com.google.ads.mediation:vungle` | 7.7.8.1 |
|
|
64
|
+
|
|
65
|
+
Each complement declares one adapter coordinate. InMobi and Liftoff also pin OkHttp 4.12.0. This replaces InMobi’s 3.14.9 and lifts Liftoff’s Okio dependency above the affected 3.0.0 release while retaining the okhttp3 API. Generated POMs propagate these versions and the matching network SDK into the consuming application. The core POM declares GMA, UMP and a patched Guava utility dependency. Guava replaces the older transitive version; it adds no ad network. Inspect all six POMs and resolved consumer dependency graphs before release.
|
|
66
|
+
|
|
67
|
+
## Update policy
|
|
68
|
+
|
|
69
|
+
All six modules remain at version `1.0.0`. Dependency updates require rebuilding both platform ZIPs for every discovered module, validating checksums, checking isolated consumer combinations and testing on available devices. Verify third-party delivery separately with configured accounts and Ad Inspector.
|
|
70
|
+
|
|
71
|
+
## Transitive security review
|
|
72
|
+
|
|
73
|
+
The September 23, 2026 refresh checks resolved dependencies for all eight Android consumer combinations. Updating the ad adapters alone still selected older utility libraries, so the distributed POMs include these corrections:
|
|
74
|
+
|
|
75
|
+
| Dependency | Previous resolution | Updated resolution | Where the pin lives |
|
|
76
|
+
| --- | --- | --- | --- |
|
|
77
|
+
| Guava | `31.1-android` in core and several combinations | `33.7.1-android` | Core Android module |
|
|
78
|
+
| OkHttp | `3.14.9` with InMobi; `4.10.0` with Liftoff | `4.12.0` | InMobi and Liftoff Android complements |
|
|
79
|
+
| Okio / Okio-jvm | `3.0.0` with Liftoff | At least `3.6.0`, through OkHttp; InMobi selects `3.7.0` | Resolved transitively |
|
|
80
|
+
|
|
81
|
+
Guava's Android variant requires API 24, matching this module's existing floor. [Guava release notes](https://github.com/google/guava/releases/tag/v33.7.1) identify the published Android artifact. [OkHttp 4's compatibility guide](https://lysine.dev/okhttp/changelogs/upgrading_to_okhttp_4/) documents binary compatibility with the okhttp3 API; its [4.x changelog](https://lysine.dev/okhttp/changelogs/changelog_4x/) records the hostname-verification fix and Okio update. These are utility dependencies, not additional ad networks.
|
|
82
|
+
|
|
83
|
+
The earlier matches were `GHSA-5mg8-w23w-74h3`, `GHSA-7g45-4rm6-3mm3` (Guava), `GHSA-3cqm-mf7h-prrj` (OkHttp) and `GHSA-w33c-445m-f8w7` (Okio). The final [OSV querybatch](https://google.github.io/osv.dev/api/) scan covered 149 distinct Maven name/version pairs across all eight consumer combinations and returned no matches on September 23, 2026. A version scan does not establish provider delivery or prove that every internal SDK path is safe.
|
|
84
|
+
|
|
85
|
+
Unity `4.20.1.0` includes the adapter consent-forwarding update. Provider-side verification is described in the [mediation guide](mediation.md#unity-consent-with-the-pinned-adapter).
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
> Source snapshot: official ti.admob.unified 1.0.0 documentation at commit `5b4cbf9` (copied 2026-10-11). Relative links rebased for this skill.
|
|
2
|
+
|
|
3
|
+
# Fullscreen ads
|
|
4
|
+
|
|
5
|
+
Interstitial, rewarded and app open proxies share one public lifecycle and event contract on iOS and Android.
|
|
6
|
+
|
|
7
|
+
## When to show a fullscreen ad
|
|
8
|
+
|
|
9
|
+
A fullscreen ad interrupts the current screen, so choose its presentation point before requesting it.
|
|
10
|
+
|
|
11
|
+
Load early, show later: `load()` is a network request and can take seconds. Start it before the intended break, for example when a level opens, an article starts or a form appears. Call `show()` when the application reaches that break. Presenting inside the `loaded` listener lets network timing determine when the user is interrupted.
|
|
12
|
+
|
|
13
|
+
Show fullscreen ads at a natural break, such as between levels or after a task or flow ends. Avoid interrupting work in progress.
|
|
14
|
+
|
|
15
|
+
Google's guidance includes these restrictions:
|
|
16
|
+
|
|
17
|
+
- Do not show an interstitial as the app opens. That moment belongs to the App Open format, documented below.
|
|
18
|
+
- Do not show one on the way out, when the user is already leaving.
|
|
19
|
+
- Do not interrupt an action in progress, or place the ad where a button or a control the user was about to touch used to be.
|
|
20
|
+
- Use `createRewarded()` to reward users for watching ads. Interstitials must not offer rewards.
|
|
21
|
+
- Avoid consecutive interstitials and excessive frequency. Configure frequency capping on the ad unit in the AdMob console; JavaScript does not control it.
|
|
22
|
+
|
|
23
|
+
The full lists are in Google's [Android interstitial guide](https://developers.google.com/admob/android/interstitial) and [iOS interstitial guide](https://developers.google.com/admob/ios/interstitial).
|
|
24
|
+
|
|
25
|
+
## Privacy gate
|
|
26
|
+
|
|
27
|
+
Fullscreen proxies do not call UMP and do not enforce ad eligibility:
|
|
28
|
+
|
|
29
|
+
```js
|
|
30
|
+
if (!Admob.canRequestAds()) {
|
|
31
|
+
return
|
|
32
|
+
}
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Create and load fullscreen ads only after `module_initialized` succeeds.
|
|
36
|
+
|
|
37
|
+
## Interstitial
|
|
38
|
+
|
|
39
|
+
Load the ad ahead of its presentation point:
|
|
40
|
+
|
|
41
|
+
```js
|
|
42
|
+
const interstitial = Admob.createInterstitial({
|
|
43
|
+
adUnitId: Admob.TEST_INTERSTITIAL,
|
|
44
|
+
autoReload: false
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
interstitial.addEventListener(Admob.AD_LOADED, () => {
|
|
48
|
+
Ti.API.info('Interstitial is ready for the next break')
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
interstitial.addEventListener(Admob.AD_FAILED_TO_LOAD, event => {
|
|
52
|
+
Ti.API.warn(`Interstitial failed: ${event.error} (${event.code})`)
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
interstitial.load()
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Then present it when the application reaches the break it chose:
|
|
59
|
+
|
|
60
|
+
```js
|
|
61
|
+
function onLevelCompleted() {
|
|
62
|
+
if (interstitial && interstitial.isLoaded()) {
|
|
63
|
+
interstitial.show()
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
goToNextLevel()
|
|
67
|
+
}
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
If the ad is not ready, let the application continue and try at the next break.
|
|
71
|
+
|
|
72
|
+
The executable [example app](../assets/example/app.js) loads automatically after initialization. Each format has one button that reports its state and enables Show when ready.
|
|
73
|
+
|
|
74
|
+
## Rewarded
|
|
75
|
+
|
|
76
|
+
Reward the user only from the `rewarded` event:
|
|
77
|
+
|
|
78
|
+
```js
|
|
79
|
+
const rewarded = Admob.createRewarded({
|
|
80
|
+
adUnitId: Admob.TEST_REWARDED,
|
|
81
|
+
autoReload: false
|
|
82
|
+
})
|
|
83
|
+
|
|
84
|
+
rewarded.addEventListener(Admob.AD_REWARDED, event => {
|
|
85
|
+
grantReward(event.rewardAmount, event.rewardType)
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
rewarded.addEventListener(Admob.AD_FAILED_TO_SHOW, event => {
|
|
89
|
+
Ti.API.warn(`Rewarded ad could not show: ${event.error}`)
|
|
90
|
+
})
|
|
91
|
+
|
|
92
|
+
rewarded.load()
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
Closing a rewarded ad is not proof that the reward was earned.
|
|
96
|
+
|
|
97
|
+
## App open
|
|
98
|
+
|
|
99
|
+
Use an app open ad when the application enters the foreground, either at a cold start or when returning from the background. Load it beforehand and present it during the application's own loading state, before the user starts using the next screen:
|
|
100
|
+
|
|
101
|
+
```js
|
|
102
|
+
const appOpen = Admob.createAppOpen({
|
|
103
|
+
adUnitId: Admob.TEST_APP_OPEN,
|
|
104
|
+
autoReload: false
|
|
105
|
+
})
|
|
106
|
+
|
|
107
|
+
appOpen.addEventListener(Admob.AD_LOADED, () => {
|
|
108
|
+
Ti.API.info('App open ad is ready for the next foreground')
|
|
109
|
+
})
|
|
110
|
+
|
|
111
|
+
appOpen.load()
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
```js
|
|
115
|
+
function onApplicationResumed() {
|
|
116
|
+
if (appOpen && appOpen.isLoaded()) {
|
|
117
|
+
appOpen.show()
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
### The four hour window
|
|
123
|
+
|
|
124
|
+
Google documents that an app open ad expires four hours after it was requested. An ad rendered later is no longer valid and may not earn revenue, so the proxy enforces the window itself, with no timer and no extra event:
|
|
125
|
+
|
|
126
|
+
- The load time is recorded when the ad arrives.
|
|
127
|
+
- `isLoaded()` returns `false` once four hours have passed.
|
|
128
|
+
- `show()` on an expired ad clears the native reference and emits `failed_to_show` with code `1` and the message `App open ad expired after four hours`.
|
|
129
|
+
- `load()` discards an expired ad before requesting a new one, so a reload after expiry is not swallowed by the duplicate-load guard.
|
|
130
|
+
|
|
131
|
+
`autoReload` reloads only after `closed`. The application decides when to replace an expired ad. Expiry checks subtract the stored time when called; they do not poll in the background.
|
|
132
|
+
|
|
133
|
+
## Creation properties
|
|
134
|
+
|
|
135
|
+
All three factories accept:
|
|
136
|
+
|
|
137
|
+
| Property | Type | Default | Notes |
|
|
138
|
+
| -------------- | -------- | ------- | ------------------------------------------------------------------ |
|
|
139
|
+
| `adUnitId` | String | `""` | Required before `load()`. |
|
|
140
|
+
| `autoReload` | Boolean | `false` | Loads a fresh ad after a successful close, and after nothing else. |
|
|
141
|
+
| `keywords` | String[] | None | Request keywords, applied as targeting on both platforms. |
|
|
142
|
+
| `requestAgent` | String | None | Identifies what issued the request, on both platforms. |
|
|
143
|
+
| `extras` | Object | None | Forwarded through Google ad-network extras on both platforms. |
|
|
144
|
+
|
|
145
|
+
Do not use `extras` to bypass UMP or synthesize personalization state.
|
|
146
|
+
|
|
147
|
+
Both platforms preserve primitive values in `extras`. Use the types required by the receiving adapter; for example, pass `{ npa: '1' }` when it expects a string.
|
|
148
|
+
|
|
149
|
+
Pass initial configuration to the factory. Later assignments to the five configuration properties affect the next request on both platforms. An already loaded ad keeps the configuration of its original request.
|
|
150
|
+
|
|
151
|
+
## Shared methods
|
|
152
|
+
|
|
153
|
+
| Method | Description |
|
|
154
|
+
| ------------ | --------------------------------------------------------------------------------------------------------------- |
|
|
155
|
+
| `load()` | Starts one request. Calls while loading, loaded, or showing are ignored. App open discards an expired ad first. |
|
|
156
|
+
| `show()` | Presents the loaded ad or emits `failed_to_show`. |
|
|
157
|
+
| `isLoaded()` | Returns `true` only while an unused ad is ready. For app open, an ad older than four hours is not ready. |
|
|
158
|
+
| `destroy()` | Terminal, idempotent cleanup; emits `destroyed` once. |
|
|
159
|
+
|
|
160
|
+
Google fullscreen ads are single-use. After a presentation or a show failure the proxy holds no ad: its native reference is cleared and `isLoaded()` turns `false`. Call `load()` again before the next `show()`.
|
|
161
|
+
|
|
162
|
+
Late native callbacks are ignored after `destroy()`. A dismissed or failed-to-show ad reference is cleared before the terminal event is emitted.
|
|
163
|
+
|
|
164
|
+
On both platforms, `load()` after `destroy()` logs a warning without emitting an event. Banners emit `failed_to_load` in this case, so a handler must account for the proxy type.
|
|
165
|
+
|
|
166
|
+
On Android, `load()` and `show()` need a current activity, and without one they emit `failed_to_load` or `failed_to_show` with the message `No current activity available` and code `-1`. It happens when the application calls in with no activity in the foreground. Code `-1` is the module's own, outside Google's error-code range.
|
|
167
|
+
|
|
168
|
+
## Keeping the next ad ready
|
|
169
|
+
|
|
170
|
+
For repeated presentations in one session, load the next ad after the previous one closes so it can be ready at the next break:
|
|
171
|
+
|
|
172
|
+
```js
|
|
173
|
+
interstitial.addEventListener(Admob.AD_CLOSED, () => {
|
|
174
|
+
interstitial.load()
|
|
175
|
+
})
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
`autoReload: true` reloads after `closed` only. The application handles retries after `failed_to_load` and `failed_to_show`. Use backoff: a `NO_FILL` response will often still be `NO_FILL` a second later.
|
|
179
|
+
|
|
180
|
+
Reuse the proxy across loads. Create a new one only after `destroy()`.
|
|
181
|
+
|
|
182
|
+
## Events
|
|
183
|
+
|
|
184
|
+
### Common events
|
|
185
|
+
|
|
186
|
+
| Event | Interstitial | Rewarded | App open | Common payload |
|
|
187
|
+
| --------------------------- | -----------: | -------: | -------: | -------------------------------------------------- |
|
|
188
|
+
| `loaded` | Yes | Yes | Yes | `adUnitId`, `adType` |
|
|
189
|
+
| `failed_to_load` | Yes | Yes | Yes | `adUnitId`, `adType`, `error`, `message`, `code` |
|
|
190
|
+
| `opened` | Yes | Yes | Yes | `adUnitId`, `adType` |
|
|
191
|
+
| `showed_fullscreen_content` | Yes | Yes | Yes | `adUnitId`, `adType` |
|
|
192
|
+
| `closed` | Yes | Yes | Yes | `adUnitId`, `adType` |
|
|
193
|
+
| `clicked` | Yes | Yes | Yes | `adUnitId`, `adType` |
|
|
194
|
+
| `impression` | Yes | Yes | Yes | `adUnitId`, `adType` |
|
|
195
|
+
| `failed_to_show` | Yes | Yes | Yes | `adUnitId`, `adType`, `error`, `message`, `code` |
|
|
196
|
+
| `destroyed` | Yes | Yes | Yes | `adUnitId`, `adType` |
|
|
197
|
+
| `rewarded` | No | Yes | No | `adUnitId`, `adType`, `rewardType`, `rewardAmount` |
|
|
198
|
+
|
|
199
|
+
`message` duplicates `error` for compatibility. New code may use `error`; both keys are portable.
|
|
200
|
+
|
|
201
|
+
## Cleanup
|
|
202
|
+
|
|
203
|
+
Use named callbacks and remove them before disposing the proxy:
|
|
204
|
+
|
|
205
|
+
```js
|
|
206
|
+
function disposeInterstitial() {
|
|
207
|
+
interstitial.removeEventListener(Admob.AD_LOADED, onLoaded)
|
|
208
|
+
interstitial.removeEventListener(Admob.AD_FAILED_TO_LOAD, onFailed)
|
|
209
|
+
interstitial.removeEventListener(Admob.AD_CLOSED, onClosed)
|
|
210
|
+
interstitial.destroy()
|
|
211
|
+
}
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
Create a new proxy after disposal; a destroyed proxy cannot load again.
|
|
215
|
+
|
|
216
|
+
## Not implemented
|
|
217
|
+
|
|
218
|
+
The module exports format-name and test-ID constants for future compatibility, but it does not expose working factories for:
|
|
219
|
+
|
|
220
|
+
- Rewarded interstitial
|
|
221
|
+
- Native
|
|
222
|
+
|
|
223
|
+
`TEST_REWARDED_INTERSTITIAL` and `TEST_NATIVE` name units no factory accepts.
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# Game monetization and module integration
|
|
2
|
+
|
|
3
|
+
Use the official `fullscreen.md`, `privacy.md` and `library.md` contract for calls. This reference adds product/lifecycle criteria, not a second module API. The detailed asset-to-game coordination guide is `ti-game-assets/references/responsible-ads.md` when that skill is available; this skill works independently of it.
|
|
4
|
+
|
|
5
|
+
## Community-Discovered Patterns
|
|
6
|
+
|
|
7
|
+
These are author guidance and integration lessons from Lander/Guardian, not additional module API or a verified optimal monetization policy. Preserve this section during official-doc refreshes and re-evaluate its lifecycle claims against current builds.
|
|
8
|
+
|
|
9
|
+
Choose deliberate natural breaks, such as settled end-of-run results, rather than interrupting flight/combat or attaching an ad to every retry. Preserve pause/tutorial/practice and voluntary-exit choices. Load early, but only the application decides to show; reaching a time threshold makes a later boundary eligible, never a timer-based interruption. Current [Google guidance](https://support.google.com/admob/answer/6201362?hl=en) disallows unexpected and excessive interstitial placements; re-check it before a live change.
|
|
10
|
+
|
|
11
|
+
The ad boundary must suspend/release native motion and sound as appropriate. A stopped JS tick does not stop native gravity. Resume only the intended scene after an actual dismissal or confirmed show failure. One director owns presentation, protects against simultaneous attempts, and discards continuations after the screen/session closes. Timeouts may handle missing opening; they must not resume gameplay behind a confirmed open ad. A background/resume or late load callback does not start a missed ad.
|
|
12
|
+
|
|
13
|
+
Keep grace period, active-play accumulation, cooldown after dismissal, hourly/daily caps and permitted boundaries distinct. Count actual openings once and track close separately. Preserve persistence and conservative handling of clock rollback. Lander and Guardian implement this separation, but their numeric thresholds are initial settings without evidence of an optimum; do not transplant them into every game. Caps are ceilings, not exposure targets. Application eligibility is separate from the network's AdMob frequency cap.
|
|
14
|
+
|
|
15
|
+
Use test IDs and the official consent-first flow. Banners need layout away from controls; rewarded ads need an explicit voluntary offer, a real reward and one grant from the documented rewarded event. Interstitials are not rewarded ads. Do not add forced rewarded continues, App Open or a new format without product scope. The module's implemented formats and roadmap determine what is possible.
|
|
16
|
+
|
|
17
|
+
Test unavailable module, no fill, load/show failures, repeated taps, late/duplicate callbacks, real close, background/resume, destruction and phone/tablet layout on both platforms. An SDK opening/impression does not prove the creative was visible: physical visual acceptance is separate. Measure confirmed exposure per active-play minute, abandonment at ad boundaries and retention/retries where telemetry is authorized; unit tests do not prove players enjoy the frequency.
|
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
> Source snapshot: official ti.admob.unified 1.0.0 documentation at commit `5b4cbf9` (copied 2026-10-11). Relative links rebased for this skill.
|
|
2
|
+
|
|
3
|
+
# Getting started
|
|
4
|
+
|
|
5
|
+
## 1. Install the modules
|
|
6
|
+
|
|
7
|
+
Install the `1.0.0` ZIP of `ti.admob.unified` for every platform you build. All artifacts of a given module must have the same module ID and version:
|
|
8
|
+
|
|
9
|
+
```xml
|
|
10
|
+
<modules>
|
|
11
|
+
<module version="1.0.0">ti.admob.unified</module>
|
|
12
|
+
</modules>
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
An application that serves AdMob demand needs only the core: about 14 MB on iOS and 325 KB on Android, with no third-party network SDKs in the build.
|
|
16
|
+
|
|
17
|
+
If the application mediates, install the selected per-network complements at the same version. For AppLovin:
|
|
18
|
+
|
|
19
|
+
```xml
|
|
20
|
+
<modules>
|
|
21
|
+
<module version="1.0.0">ti.admob.unified</module>
|
|
22
|
+
<module version="1.0.0">ti.admob.unified.mediation.applovin</module>
|
|
23
|
+
</modules>
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Each complement carries one adapter and its dependencies, with no JavaScript API. Add a module line for each intended network; leave unneeded networks out. See [AdMob mediation](mediation.md).
|
|
27
|
+
|
|
28
|
+
The current baselines are Titanium `13.2.0.GA`, iOS `15.0+`, and Android API `24+`.
|
|
29
|
+
|
|
30
|
+
For an existing AdMob integration, see [Migrating to ti.admob.unified](migration.md).
|
|
31
|
+
|
|
32
|
+
## 2. Configure the AdMob application IDs
|
|
33
|
+
|
|
34
|
+
An AdMob **application ID** contains a tilde:
|
|
35
|
+
|
|
36
|
+
```text
|
|
37
|
+
ca-app-pub-0000000000000000~0000000000
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
An **ad unit ID** contains a slash:
|
|
41
|
+
|
|
42
|
+
```text
|
|
43
|
+
ca-app-pub-0000000000000000/0000000000
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
They are not interchangeable. The application ID belongs in `tiapp.xml`; ad unit IDs are supplied to ad factories such as `createAdaptiveBanner()`.
|
|
47
|
+
|
|
48
|
+
### iOS
|
|
49
|
+
|
|
50
|
+
Add the iOS application ID to the app plist section:
|
|
51
|
+
|
|
52
|
+
```xml
|
|
53
|
+
<ios>
|
|
54
|
+
<plist>
|
|
55
|
+
<dict>
|
|
56
|
+
<key>GADApplicationIdentifier</key>
|
|
57
|
+
<string>ca-app-pub-0000000000000000~0000000000</string>
|
|
58
|
+
</dict>
|
|
59
|
+
</plist>
|
|
60
|
+
</ios>
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
App Tracking Transparency is optional and separate from UMP. Most banner applications do not need it: serving ads does not require tracking users across other companies' apps and websites. Add the usage description only when the application uses tracking, and describe that use accurately. Apple reviews the prompt against the app's behavior.
|
|
64
|
+
|
|
65
|
+
```xml
|
|
66
|
+
<ios>
|
|
67
|
+
<plist>
|
|
68
|
+
<dict>
|
|
69
|
+
<key>NSUserTrackingUsageDescription</key>
|
|
70
|
+
<string>This identifier helps us measure advertising performance.</string>
|
|
71
|
+
</dict>
|
|
72
|
+
</plist>
|
|
73
|
+
</ios>
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
Omit that key entirely if the application never calls `requestTrackingAuthorization()`. UMP still runs and still shows the consent form where the region requires one; the two mechanisms are unrelated.
|
|
77
|
+
|
|
78
|
+
Keep any required `SKAdNetworkItems` in the consuming app. Follow Google's [iOS setup guide](https://developers.google.com/admob/ios/quick-start) for the current list and App Store requirements.
|
|
79
|
+
|
|
80
|
+
### Android
|
|
81
|
+
|
|
82
|
+
Merge the Android application ID into the application element:
|
|
83
|
+
|
|
84
|
+
```xml
|
|
85
|
+
<android xmlns:android="http://schemas.android.com/apk/res/android">
|
|
86
|
+
<manifest>
|
|
87
|
+
<application>
|
|
88
|
+
<meta-data
|
|
89
|
+
android:name="com.google.android.gms.ads.APPLICATION_ID"
|
|
90
|
+
android:value="ca-app-pub-0000000000000000~0000000000"/>
|
|
91
|
+
</application>
|
|
92
|
+
</manifest>
|
|
93
|
+
</android>
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
Omitting this metadata causes the native Google Mobile Ads SDK to fail with a missing application ID. See Google's [Android setup guide](https://developers.google.com/admob/android/quick-start).
|
|
97
|
+
|
|
98
|
+
Do not pass `applicationId` to `initialize()`; the module does not consume that property.
|
|
99
|
+
|
|
100
|
+
## 3. Configure mediation if needed
|
|
101
|
+
|
|
102
|
+
Skip this step unless the application already has, or is about to have, an AdMob mediation group with real ad sources in it. Installing adapters does not raise fill on its own: a network participates only once its publisher account is approved, it is added as an ad source, and its credentials are mapped to the ad unit.
|
|
103
|
+
|
|
104
|
+
Install the corresponding complements from step 1 when the application will mediate. AppLovin, Meta, InMobi, Unity and Liftoff are available on both platforms; ironSource mediation is deferred. Add the intended networks to the AdMob mediation groups and complete their account, placement, privacy and store-disclosure setup.
|
|
105
|
+
|
|
106
|
+
No platform-specific JavaScript or manual adapter initialization is required. After `module_initialized`, use `getMediationStatus()` to inspect the adapter states reported by Google; check package contents separately. See [AdMob mediation](mediation.md).
|
|
107
|
+
|
|
108
|
+
### Automatic adapter discovery
|
|
109
|
+
|
|
110
|
+
After the privacy gate, use `Admob.initialize({})` with either the core alone or selected complements. Google discovers the adapters included by `tiapp.xml`; no mediation flag or manual provider initialization is required.
|
|
111
|
+
|
|
112
|
+
Install only networks with the intended accounts and AdMob mappings configured. An installed adapter with incomplete configuration may delay startup until it responds or times out. For exceptional sessions that deliberately skip installed adapters, see the [initialization reference](api-reference.md#automatic-adapter-discovery).
|
|
113
|
+
|
|
114
|
+
## 4. Configure Privacy & messaging
|
|
115
|
+
|
|
116
|
+
Create the required messages in the **Privacy & messaging** section of the AdMob console. UMP resolves the message configuration from the application ID in the consuming app.
|
|
117
|
+
|
|
118
|
+
The application must request a fresh consent-information update on every launch. See [Privacy and initialization](privacy.md) before loading any ad.
|
|
119
|
+
|
|
120
|
+
## 5. Use official test units
|
|
121
|
+
|
|
122
|
+
The `TEST_*` constants resolve to Google's platform-specific demo ad unit IDs:
|
|
123
|
+
|
|
124
|
+
```js
|
|
125
|
+
const Admob = require('ti.admob.unified')
|
|
126
|
+
|
|
127
|
+
const adaptiveId = Admob.TEST_ADAPTIVE_BANNER
|
|
128
|
+
const interstitialId = Admob.TEST_INTERSTITIAL
|
|
129
|
+
const rewardedId = Admob.TEST_REWARDED
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
The module exports the full set of test constants for compatibility. Two formats have no implemented factory:
|
|
133
|
+
|
|
134
|
+
| Constant | Factory that accepts it |
|
|
135
|
+
| ---------------------------- | ------------------------------------------------- |
|
|
136
|
+
| `TEST_BANNER` | `createBanner()` |
|
|
137
|
+
| `TEST_ADAPTIVE_BANNER` | `createAdaptiveBanner()` |
|
|
138
|
+
| `TEST_INTERSTITIAL` | `createInterstitial()` |
|
|
139
|
+
| `TEST_INTERSTITIAL_VIDEO` | `createInterstitial()` (same API, video creative) |
|
|
140
|
+
| `TEST_REWARDED` | `createRewarded()` |
|
|
141
|
+
| `TEST_REWARDED_INTERSTITIAL` | None. Reserved; the format is not implemented |
|
|
142
|
+
| `TEST_NATIVE` | None. Reserved; the format is not implemented |
|
|
143
|
+
| `TEST_APP_OPEN` | `createAppOpen()` |
|
|
144
|
+
|
|
145
|
+
See [Reserved and unavailable APIs](api-reference.md#reserved-and-unavailable-apis).
|
|
146
|
+
|
|
147
|
+
Do not copy an Android demo ID into an iOS branch or the reverse. Let the module constant select the correct ID.
|
|
148
|
+
|
|
149
|
+
Google recommends demo units or explicitly registered test devices during development. See the official [Android test-ad guide](https://developers.google.com/admob/android/test-ads) and [iOS test-ad guide](https://developers.google.com/admob/ios/test-ads).
|
|
150
|
+
|
|
151
|
+
## 6. Request consent, then initialize
|
|
152
|
+
|
|
153
|
+
Register all listeners first:
|
|
154
|
+
|
|
155
|
+
```js
|
|
156
|
+
const Admob = require('ti.admob.unified')
|
|
157
|
+
|
|
158
|
+
let initialized = false
|
|
159
|
+
|
|
160
|
+
function initializeOnce() {
|
|
161
|
+
if (initialized || !Admob.canRequestAds()) {
|
|
162
|
+
return
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
initialized = true
|
|
166
|
+
Admob.initialize({
|
|
167
|
+
ageRestrictedTreatment: Admob.AGE_RESTRICTED_TREATMENT_UNSPECIFIED,
|
|
168
|
+
maxAdContentRating: Admob.MAX_AD_CONTENT_RATING_GENERAL
|
|
169
|
+
})
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
Admob.addEventListener('module_initialized', event => {
|
|
173
|
+
if (event.success) {
|
|
174
|
+
Ti.API.info('Google Mobile Ads initialized')
|
|
175
|
+
} else {
|
|
176
|
+
Ti.API.warn(`Google Mobile Ads initialization failed: ${event.error}`)
|
|
177
|
+
}
|
|
178
|
+
})
|
|
179
|
+
|
|
180
|
+
Admob.addEventListener('consent_complete', event => {
|
|
181
|
+
if (event.canRequestAds) {
|
|
182
|
+
initializeOnce()
|
|
183
|
+
}
|
|
184
|
+
})
|
|
185
|
+
|
|
186
|
+
Admob.requestConsent({
|
|
187
|
+
tagForUnderAgeOfConsent: false
|
|
188
|
+
})
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
### Optional: if the application requests ATT
|
|
192
|
+
|
|
193
|
+
Skip this section unless step 2 added `NSUserTrackingUsageDescription`. ATT is not a prerequisite for serving ads, and requesting it without a real tracking case invites an App Store rejection.
|
|
194
|
+
|
|
195
|
+
An application that does request it waits for the Apple prompt to settle before initializing, so that the SDK reads a final tracking status:
|
|
196
|
+
|
|
197
|
+
```js
|
|
198
|
+
Admob.addEventListener('consent_complete', event => {
|
|
199
|
+
if (!event.canRequestAds) {
|
|
200
|
+
return
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
const onTrackingComplete = trackingEvent => {
|
|
204
|
+
Admob.removeEventListener(
|
|
205
|
+
'tracking_authorization_completed',
|
|
206
|
+
onTrackingComplete
|
|
207
|
+
)
|
|
208
|
+
Ti.API.info(`ATT status: ${trackingEvent.status}`)
|
|
209
|
+
initializeOnce()
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
Admob.addEventListener(
|
|
213
|
+
'tracking_authorization_completed',
|
|
214
|
+
onTrackingComplete
|
|
215
|
+
)
|
|
216
|
+
Admob.requestTrackingAuthorization()
|
|
217
|
+
})
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
Google Mobile Ads reads `trackingAuthorizationStatus` on its own on iOS. That read is not a permission request and does not open Apple's prompt: only `requestTrackingAuthorization()` does.
|
|
221
|
+
|
|
222
|
+
`initialize()` and `requestConsent()` are idempotent native operations. Application-level guards are still useful because cached terminal events may be emitted again when a caller invokes an already-completed operation.
|
|
223
|
+
|
|
224
|
+
## 7. Create an ad only after the gate opens
|
|
225
|
+
|
|
226
|
+
A banner needs a host view that is already in the hierarchy, so the native view can measure its real width.
|
|
227
|
+
|
|
228
|
+
Reserve the host height before the ad arrives. Growing it from zero when `loaded` fires moves surrounding content after the screen appears. Give the host its expected height and a background colour that matches the screen, so the empty slot looks like a bottom margin:
|
|
229
|
+
|
|
230
|
+
```js
|
|
231
|
+
const bannerHost = Ti.UI.createView({
|
|
232
|
+
left: 0,
|
|
233
|
+
right: 0,
|
|
234
|
+
bottom: 0,
|
|
235
|
+
height: '50dp'
|
|
236
|
+
})
|
|
237
|
+
|
|
238
|
+
if (Admob.canRequestAds()) {
|
|
239
|
+
const banner = Admob.createAdaptiveBanner({
|
|
240
|
+
adUnitId: Admob.TEST_ADAPTIVE_BANNER,
|
|
241
|
+
adaptiveType: Admob.ADAPTIVE_ANCHORED,
|
|
242
|
+
width: Ti.UI.FILL
|
|
243
|
+
})
|
|
244
|
+
|
|
245
|
+
banner.addEventListener(Admob.AD_LOADED, event => {
|
|
246
|
+
bannerHost.height = `${event.height}dp`
|
|
247
|
+
})
|
|
248
|
+
|
|
249
|
+
bannerHost.add(banner)
|
|
250
|
+
banner.load()
|
|
251
|
+
}
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
A fixed `BANNER` is `50dp` and `FULLBANNER` is `60dp`, so a fixed-size placement can reserve its exact height. An adaptive banner reports its final height after loading. Reserve a suitable minimum, then adjust it in the loaded event to avoid growing the host from zero.
|
|
255
|
+
|
|
256
|
+
[Banner ads](banners.md) covers the full placement contract: reserving layout space without overlapping bottom-aligned UI, the named-listener lifecycle, and `pause()` / `resume()` / `destroy()`.
|
|
257
|
+
|
|
258
|
+
The module does not enforce the privacy gate inside ad proxies. The consuming application owns the order.
|
|
259
|
+
|
|
260
|
+
## Common setup mistakes
|
|
261
|
+
|
|
262
|
+
- Passing an ad unit ID where an application ID is required.
|
|
263
|
+
- Passing `applicationId` or `debugGeography` to `initialize()`.
|
|
264
|
+
- Loading ads before `consent_complete` and `canRequestAds === true`.
|
|
265
|
+
- Using `canShowPersonalizedAds()` or `isGDPR()` as a gate.
|
|
266
|
+
- Hardcoding an adaptive banner height.
|
|
267
|
+
- Keeping banner listeners after destroying or removing a placement.
|
|
268
|
+
- Using a production ad unit while clicking ads during development.
|
|
269
|
+
- Requesting ATT, or shipping `NSUserTrackingUsageDescription`, in an app with no tracking case. UMP does not require either one.
|
|
270
|
+
- Installing mediation complements without configured ad sources adds SDK size and may delay startup while unused networks time out.
|
|
271
|
+
- Publishing an app that is supposed to mediate without the selected complements installed, which must be checked through package inventories and Ad Inspector; an empty diagnostic map alone is not an installation check.
|
|
272
|
+
|
|
273
|
+
## Example appearance
|
|
274
|
+
|
|
275
|
+
For the classic playground, copy `example/app.js` and `example/semantic.colors.json` into `Resources/`, and `example/lib/ads.js` into `Resources/lib/`. The named colors provide light and dark variants for the content and window title. Keep the default DayNight Android theme so system icons follow the selected appearance. The sample keeps the title and status bars visible. Changing appearance does not require rebuilding the ad proxies or resetting the scroll position.
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
> Source snapshot: official ti.admob.unified 1.0.0 documentation at commit `5b4cbf9` (copied 2026-10-11). Relative links rebased for this skill.
|
|
2
|
+
|
|
3
|
+
# ti.admob.unified documentation
|
|
4
|
+
|
|
5
|
+
This documentation describes the public JavaScript contract implemented by `ti.admob.unified` `1.0.0`.
|
|
6
|
+
|
|
7
|
+
## Start here
|
|
8
|
+
|
|
9
|
+
1. [Getting started](getting-started.md): install the platform artifacts, configure the AdMob application IDs, and make a first test request.
|
|
10
|
+
2. [Integrating the module](integration.md): the boundary between native lifecycle, application navigation, layout, and refresh ownership.
|
|
11
|
+
3. [Copy-ready JavaScript library](library.md): copy one maintained file into `lib/` for startup, every implemented ad format, privacy, and cleanup.
|
|
12
|
+
4. [Privacy and initialization](privacy.md): implement UMP, ATT, the `canRequestAds()` gate, and privacy options correctly.
|
|
13
|
+
5. [AdMob mediation](mediation.md): install selected complements, and configure and validate the five networks without platform-specific JavaScript.
|
|
14
|
+
6. [Banner ads](banners.md): integrate standard or adaptive banners with real layout space and deterministic cleanup.
|
|
15
|
+
7. [Fullscreen ads](fullscreen.md): interstitial, rewarded, and app open ads on one lifecycle, and when it is acceptable to interrupt the user.
|
|
16
|
+
|
|
17
|
+
## Reference
|
|
18
|
+
|
|
19
|
+
- [Complete API reference](api-reference.md): module methods, proxy methods, events, payloads, constants, compatibility APIs, and platform notes.
|
|
20
|
+
- [Migrating to ti.admob.unified](migration.md): replace `ti.admob`, `ti.android.admob`, or an internal pre-release build.
|
|
21
|
+
- [Third-party SDK notices](third-party-notices.md): licenses and terms for the distributed advertising SDKs.
|
|
22
|
+
|
|
23
|
+
- [Adding a network](contributing.md#adding-a-network): package and validate an existing official mediation adapter.
|
|
24
|
+
|
|
25
|
+
## Project information
|
|
26
|
+
|
|
27
|
+
- [Dependency inventory](dependencies.md)
|
|
28
|
+
- [Changelog](module-changelog.md)
|
|
29
|
+
- [Roadmap](roadmap.md)
|
|
30
|
+
- [Contributing](contributing.md)
|