|
| 1 | +<!-- AGENT_DIRECTIVES v1 --> |
| 2 | + |
| 3 | +Priority: Follow these directives unless they conflict with system/developer instructions or safety rules. |
| 4 | + |
| 5 | +## System Context |
| 6 | + |
| 7 | +You are an AI agent specialized in Kotlin (Android/Compose) and JavaScript development. This document defines mandatory rules for consistent, secure, and maintainable development. Follow the sections below precisely: |
| 8 | + |
| 9 | +- Project Overview |
| 10 | +- Pre-Implementation Checklist |
| 11 | +- Bridge Rules |
| 12 | +- Code Style Guidelines |
| 13 | +- ktlint Compliance |
| 14 | +- Development Best Practices |
| 15 | +- Kotlin Coroutines |
| 16 | +- Project Structure |
| 17 | +- Style Lifecycle |
| 18 | +- Error Handling |
| 19 | +- Privacy |
| 20 | +- Tests |
| 21 | +- Pull Request Template |
| 22 | + |
| 23 | +## Project Overview |
| 24 | + |
| 25 | +This Android library wraps the MapTiler JS SDK via a typed Kotlin↔JS bridge located in `MapTilerSDK/src/main/java/com/maptiler/maptilersdk/bridge`. It renders a web map inside an Android `WebView` hosted by a Compose `MTMapView` and managed by `MTMapViewController`. The HTML container (`src/main/assets/MapTilerMap.html`) loads `maptiler-sdk.umd.min.js`. Kotlin APIs translate to JS via `MTCommand`, executed by `MTBridge` through `WebViewExecutor`. The JS SDK API reference is embedded under `assets` (open `maptiler-sdk.umd.min.js.map` for sources mapping) and the JS docs live in the JS repo; search in this repo and JS bundle for the needed API. Always consult the docs/bundle before wrapping any function. |
| 26 | + |
| 27 | +### Main Components |
| 28 | + |
| 29 | +- UI (Compose): `MTMapView` is a Compose `@Composable` that attaches a configured `WebView`. |
| 30 | +- UI (XML): `MTMapViewClassic` is a `FrameLayout`-based view that inflates a `WebView` from `res/layout/mtmapview_layout.xml`. Initialize it by calling `initialize(referenceStyle, options, controller, styleVariant)` after inflation. |
| 31 | +- Controller: `MTMapViewController` binds coroutines, holds `MTStyle` and `MTMapOptions`, and exposes navigation/style APIs and events. |
| 32 | +- Bridge: Public Kotlin APIs construct `MTCommand`s serialized to JS, executed by `WebViewExecutor`; results decode via `MTBridgeReturnType`. |
| 33 | +- Lifecycle: `EventProcessor` listens to JS events, updates state, and notifies `MTMapViewDelegate` (`ON_READY`, `ON_IDLE`, etc.). |
| 34 | +- Safety: Mutate map after `ON_READY`. Style changes reapply queued layers; `MTStyle` manages layer queueing. |
| 35 | +- Responsibilities: `MTBridge` executes commands; `MTCommand` defines JS; `WebViewExecutor` calls `evaluateJavascript` on `WebView` on the main thread. |
| 36 | + |
| 37 | +## Pre-Implementation Checklist |
| 38 | +Before writing ANY new code, you MUST: |
| 39 | +- Search for existing related types: `rg -n "MT[TypeName]|[RelatedConcept]"` in this repo. |
| 40 | +- Read similar implementations completely (commands, workers, options, style, types). |
| 41 | +- Identify established patterns and shared types to reuse (e.g., options models, helpers, workers). |
| 42 | +- Confirm no existing types can be reused before creating new ones. |
| 43 | +- Follow ktlint rules enforced by the Gradle plugin. |
| 44 | +- Run `./gradlew ktlintCheck` and fix all violations locally. Optionally run `./gradlew :MapTilerSDK:ktlintCheck` for module scope. |
| 45 | +- If you add public API, add KDoc for it and ensure Dokka can generate docs (`./gradlew :MapTilerSDK:dokkaHtml`). |
| 46 | + - For UI changes, verify both Compose (`MTMapView`) and XML (`MTMapViewClassic`) usage paths compile and behave consistently. |
| 47 | + |
| 48 | +## Bridge Rules |
| 49 | + |
| 50 | +MUST follow this end-to-end flow when wrapping a JS API into Kotlin: |
| 51 | + |
| 52 | +1) Discover and design |
| 53 | +- Read the JS API in MapTiler SDK for JS docs/bundle; determine parameters, defaults, and return type. |
| 54 | +- Define an internal Kotlin `data class` implementing `MTCommand` with strongly typed parameters. Use `@Serializable` surrogates for JSON payloads when needed. |
| 55 | + |
| 56 | +2) Encode parameters |
| 57 | +- Use `kotlinx.serialization` via `JsonConfig.json.encodeToString(...)` to build a compact JSON payload; avoid manual string concatenation where possible. |
| 58 | +- Validate/clamp numeric ranges in Kotlin prior to execution (zoom, pitch, bearing, durations). |
| 59 | + |
| 60 | +3) Implement `toJS()` |
| 61 | +- Build a `JSString` that calls the JS API. Prefer passing a single options JSON object. |
| 62 | +- If easing functions or callbacks are needed, express as a JS expression string compatible with the bundle. |
| 63 | + |
| 64 | +4) Choose execution and return type |
| 65 | +- For commands with no meaningful return: set `isPrimitiveReturnType = false` and just return the invocation string. |
| 66 | +- For numeric/boolean/string results: set `isPrimitiveReturnType = true`; the result decodes into `MTBridgeReturnType` variants: `DoubleValue`, `BoolValue`, `StringValue`, `StringDoubleDict`, `Null`. |
| 67 | +- If a new return shape is required, extend `MTBridgeReturnType` in a focused change with tests. |
| 68 | + |
| 69 | +5) Public API surface |
| 70 | +- Add a thin convenience method on `MTMapViewController` (or appropriate worker/service) that: |
| 71 | + - Ensures the map/style are ready (`ON_READY`). |
| 72 | + - Validates inputs and applies sensible defaults. |
| 73 | + - Launches on the appropriate coroutine scope and calls the bridge. |
| 74 | + - Uses `suspend` functions for getters and `Result`/exceptions for failures where appropriate. |
| 75 | + |
| 76 | +6) Threading and lifecycle |
| 77 | +- MUST execute JS on the main thread. `WebViewExecutor` already enforces `Dispatchers.Main`; do not bypass it. |
| 78 | +- Avoid firing commands before the `WebView`/bridge is available; prefer queuing via style/lifecycle or guard on readiness. |
| 79 | + |
| 80 | +7) Testing |
| 81 | +- Unit test: parameter encoding and range clamping. |
| 82 | +- Contract test: `toJS()` string for simple cases (e.g., duration only). |
| 83 | + |
| 84 | +Example skeleton: |
| 85 | + |
| 86 | +```kotlin |
| 87 | +@Serializable |
| 88 | +private data class RotateToOptions( |
| 89 | + val bearing: Double, |
| 90 | + val duration: Double? = null, |
| 91 | +) |
| 92 | + |
| 93 | +internal data class RotateTo( |
| 94 | + private val bearing: Double, |
| 95 | + private val durationMs: Double? = null, |
| 96 | +) : MTCommand { |
| 97 | + override val isPrimitiveReturnType: Boolean = false |
| 98 | + |
| 99 | + override fun toJS(): JSString { |
| 100 | + val opts = RotateToOptions(bearing = bearing, duration = durationMs) |
| 101 | + val json = JsonConfig.json.encodeToString(opts) |
| 102 | + return "${MTBridge.MAP_OBJECT}.rotateTo($json);" |
| 103 | + } |
| 104 | +} |
| 105 | + |
| 106 | +// Controller convenience API |
| 107 | +fun MTMapViewController.setBearing( |
| 108 | + bearing: Double, |
| 109 | + durationMs: Double? = null, |
| 110 | +) { |
| 111 | + val clamped = ((bearing % 360 + 360) % 360) |
| 112 | + coroutineScope?.launch { bridge?.execute(RotateTo(clamped, durationMs)) } |
| 113 | +} |
| 114 | +``` |
| 115 | + |
| 116 | +XML usage example (Classic view): |
| 117 | + |
| 118 | +```xml |
| 119 | +<!-- app/src/main/res/layout/activity_main.xml --> |
| 120 | +<com.maptiler.maptilersdk.map.MTMapViewClassic |
| 121 | + android:id="@+id/mtClassic" |
| 122 | + android:layout_width="match_parent" |
| 123 | + android:layout_height="match_parent" /> |
| 124 | +``` |
| 125 | + |
| 126 | +```kotlin |
| 127 | +// In Activity/Fragment after setContentView / onViewCreated |
| 128 | +val controller = MTMapViewController(requireContext()) |
| 129 | +val classic = view.findViewById<MTMapViewClassic>(R.id.mtClassic) |
| 130 | +classic.initialize( |
| 131 | + referenceStyle = MTMapReferenceStyle.SomeStyle, |
| 132 | + options = MTMapOptions(/* ... */), |
| 133 | + controller = controller, |
| 134 | + styleVariant = null, |
| 135 | +) |
| 136 | + |
| 137 | +override fun onDestroy() { |
| 138 | + super.onDestroy() |
| 139 | + controller.destroy() |
| 140 | +} |
| 141 | +``` |
| 142 | + |
| 143 | +### Add a new command (checklist) |
| 144 | +- Read target JS API and confirm params/return. |
| 145 | +- Create command data class + `@Serializable` surrogate if needed. |
| 146 | +- Implement `toJS()` with a single options object. |
| 147 | +- Set `isPrimitiveReturnType` correctly and handle `MTBridgeReturnType` if you need the value. |
| 148 | +- Add `MTMapViewController` (or worker) convenience API; validate readiness and inputs. |
| 149 | +- Tests: encoding, clamping, and `toJS()` contract. |
| 150 | + |
| 151 | +## Code Style Guidelines (MANDATORY) |
| 152 | + |
| 153 | +- Public entities use the `MT` prefix (e.g., `MTMapView`, `MTStyle`). Domain types like `LngLat` are established exceptions. |
| 154 | +- Types use PascalCase; variables/functions use camelCase. Constants use UPPER_SNAKE_CASE where applicable. |
| 155 | +- Prefer `val` over `var`; minimize mutable state; use `data class` for value objects. |
| 156 | +- 4-space indentation; default parameters at the end of parameter lists. |
| 157 | +- End files with exactly one trailing newline. |
| 158 | +- Line length: target 120 characters max (code and comments). Wrap KDoc accordingly. |
| 159 | +- Nullability: Prefer non-null types; use `?` and safe calls sparingly, with clear invariants. |
| 160 | +- Visibility: Keep implementation details `internal`/`private`. Avoid unnecessary public APIs. |
| 161 | +- Packages: `com.maptiler.maptilersdk.[area]` and mirror directory structure. |
| 162 | + |
| 163 | +## ktlint Compliance (MANDATORY) |
| 164 | +- ALWAYS follow the rules enforced by the `org.jlleitschuh.gradle.ktlint` plugin. |
| 165 | +- Key rules: spacing, imports order, trailing newline, indentation, annotation/kdoc formatting, no wildcard imports. |
| 166 | +- Pre-commit: a Git hook runs `./gradlew ktlintCheck`. Ensure zero violations before committing. |
| 167 | +- CI/Local requirement: run `./gradlew ktlintCheck` and ensure zero warnings/errors. PRs must be lint-clean. |
| 168 | +- Formatting: the project disables `ktlintFormat` by default; fix violations manually or enable formatting locally if needed. |
| 169 | + |
| 170 | +## Development Best Practices |
| 171 | + |
| 172 | +- API surface: use `public` only for intended external API; default to `internal` within the SDK module. |
| 173 | +- Encapsulation: keep workers/services internal to their domains (navigation, zoomable, style, gestures). |
| 174 | +- Errors: represent SDK failures with typed `MTError` and propagate meaningful messages. |
| 175 | +- Documentation: document every public declaration with concise KDoc (`/** ... */`). |
| 176 | +- Tests: prioritize bridge commands, options encoding, and `toJS()` contracts. |
| 177 | +- Logging: use `MTLogger` with appropriate `MTLogType`; never log secrets. |
| 178 | + |
| 179 | +## Kotlin Coroutines |
| 180 | + |
| 181 | +- Main thread safety: |
| 182 | + - Interacting with `WebView` must occur on `Dispatchers.Main` (handled in `WebViewExecutor`). |
| 183 | + - Compose UI code runs on the main thread; do not perform blocking work in Composables. |
| 184 | +- Scope: |
| 185 | + - Use the controller’s bound `CoroutineScope` for map operations. |
| 186 | + - Prefer `suspend` APIs for bridge reads (e.g., getters like `getZoom()`). |
| 187 | +- Sendability/Immutability: |
| 188 | + - Use immutable `data class` models; avoid shared mutable state across coroutines. |
| 189 | + - Avoid `@OptIn(DelicateCoroutinesApi::class)`; keep structured concurrency. |
| 190 | + |
| 191 | +## Project Structure |
| 192 | + |
| 193 | +### Top-Level |
| 194 | + |
| 195 | +- `README.md`: Usage, Compose snippet, sources/layers, annotations, installation. |
| 196 | +- `CHANGELOG.md`, `CONTRIBUTING.md`, `LICENSE.txt`: Project meta. |
| 197 | +- Gradle: `build.gradle.kts`, `settings.gradle.kts`, `gradle.properties`, wrapper. |
| 198 | +- Lint/Docs: ktlint plugin configured in Gradle; Dokka for KDoc. |
| 199 | +- `.github/`, `scripts/`: CI, hooks, and scripts scaffolding. |
| 200 | + |
| 201 | +### Library: `MapTilerSDK` |
| 202 | + |
| 203 | +- `map/`: Core UI and map API. |
| 204 | + - `MTMapView` (Compose) attaches a `WebView`; exposes map/style APIs via controller and lifecycle events. |
| 205 | + - `MTMapViewClassic` (XML) is a `FrameLayout` that inflates a `WebView` from `res/layout/mtmapview_layout.xml` and must be initialized via `initialize(...)`. |
| 206 | + - `MTMapViewController`: camera, events, gesture service, workers binding. |
| 207 | + - `MTMapOptions` + `options/`: camera, padding, animation, gestures config. |
| 208 | + - `style/`: `MTStyle`, reference styles/variants, glyphs/terrain/tile scheme, style errors. |
| 209 | + - `gestures/`: gesture types and services (pan, pinch/rotate/zoom, double tap). |
| 210 | + - `types/`: shared types (e.g., `LngLat`, `MTPoint`, colors, source data). |
| 211 | +- `bridge/`: Kotlin ↔ JS bridge via `WebView`. |
| 212 | + - `MTCommand`, `MTBridge`, `WebViewExecutor`, `MTBridgeReturnType`, `MTError`. |
| 213 | + - `MTJavaScriptInterface` for events and error propagation from JS. |
| 214 | +- `commands/`: Strongly-typed wrappers that turn Kotlin calls into JS invocations. |
| 215 | + - `navigation/`: flyTo, easeTo, jumpTo, pan/zoom/bearing/pitch/roll, bounds, padding. |
| 216 | + - `style/`: add/remove sources and layers, set style, language, light, glyphs, projection, terrain. |
| 217 | + - `annotations/`: add/remove markers and text popups, set coordinates, batch ops. |
| 218 | + - `gestures/`: enable/disable gesture types. |
| 219 | +- `annotations/`: Public annotation APIs (`MTMarker`, `MTTextPopup`, base `MTAnnotation`). |
| 220 | +- `events/`: Event pipeline that feeds `MTMapViewDelegate` and content delegates. |
| 221 | +- `helpers/`: serialization config, color/coordinates converters, image helpers. |
| 222 | +- `logging/`: `MTLogger`, types, and adapters. |
| 223 | +- `assets/`: Embedded JS/CSS/HTML for the web map container. |
| 224 | + |
| 225 | +### Tests |
| 226 | + |
| 227 | +- `MapTilerSDK/src/test/java`: Unit tests using JUnit/MockK. |
| 228 | + - Helpers: coordinate and color conversions, camera helpers. |
| 229 | + - Suites: navigation and bridge tests (toJS contracts, encoding, return type handling). |
| 230 | + |
| 231 | +### Kotlin API Surface (Prefer Stronger Kotlin Types) |
| 232 | +- Prefer expressive Kotlin-first APIs that hide JS-specific details while encoding the correct JS schema under the hood. |
| 233 | +- For values that are strings in JS but have richer domain types (e.g., colors), expose ergonomic initializers and helpers. |
| 234 | +- Validate inputs and clamp numeric ranges in Kotlin before bridging to JS. |
| 235 | +- Default parameter values should reflect sensible SDK defaults. |
| 236 | + |
| 237 | +#### Developer-Facing Simplicity (MANDATORY) |
| 238 | +- Prefer Kotlin-first types over raw strings in public APIs (e.g., enums, value classes, typed models). |
| 239 | +- Numbers/angles: provide `.constant(Double)` and zoom-stop helpers where domain types exist. |
| 240 | +- Mixed unions: accept both collections and strings where JS unions require it; encode internally. |
| 241 | +- Add minimal tests to verify encoding and `toJS()` contracts for these conveniences. |
| 242 | + |
| 243 | +## Style Lifecycle |
| 244 | + |
| 245 | +MUST wait for `ON_READY` before mutating style or layers. Changing the reference style resets layers; re-add required sources/layers after style changes. Prefer batch commands where available. When enabling terrain or projection changes, verify map idleness before subsequent camera moves. |
| 246 | + |
| 247 | +## Error Handling |
| 248 | + |
| 249 | +- Retry once on transient bridge failures; log verbosely under `MTLogLevel.Debug`. |
| 250 | +- Return clear, user-facing messages with the failed command and suggested fix. |
| 251 | +- Treat unsupported return types as warnings; choose a safer path or request input. |
| 252 | + |
| 253 | +## Privacy |
| 254 | + |
| 255 | +- Never log API keys. Redact sensitive values from structured logs. |
| 256 | +- Avoid sending exact user coordinates unless necessary; round or fuzz where acceptable. |
| 257 | + |
| 258 | +## Tests |
| 259 | + |
| 260 | +- Before you make the Pull Request ALWAYS run unit tests to validate the code and fix potential issues. |
| 261 | +- Commands: |
| 262 | + - `./gradlew ktlintCheck` |
| 263 | + - `./gradlew :MapTilerSDK:test` |
| 264 | + - Optional: `./gradlew :MapTilerSDK:dokkaHtml` to validate docs generation |
| 265 | +- Add or update unit tests as required (encoding, clamping, `toJS()` contract), but leave full execution to the user/CI. |
| 266 | +- Prefer small, focused tests near the code you change; avoid introducing unrelated tests. |
| 267 | + |
| 268 | +## Glossary |
| 269 | + |
| 270 | +- `JSString`: A Kotlin `String` containing JS source to evaluate in the `WebView`. |
| 271 | +- `MTCommand`: A Kotlin type describing a JS-callable command (`toJS()` returns `JSString`). |
| 272 | +- Bridge executor: The component that feeds `JSString` into `evaluateJavascript` on the Android `WebView`. |
| 273 | +- `MTBridgeReturnType`: Decoders for typed return values from JS. |
| 274 | + |
| 275 | +Cross-reference implementation against original prompt requirements before making pull request, and make sure to follow the Pull Request Template below: |
| 276 | + |
| 277 | +## Pull Request Template |
| 278 | + |
| 279 | +[Link to related issue] |
| 280 | + |
| 281 | +## Objective |
| 282 | +What is the goal? |
| 283 | + |
| 284 | +## Description |
| 285 | +What changed, how and why? |
| 286 | + |
| 287 | +## Acceptance |
| 288 | +How were changes tested? |
0 commit comments