initial skills
This commit is contained in:
@@ -0,0 +1,311 @@
|
||||
# Abstraction Examples from Amethyst Codebase
|
||||
|
||||
Real examples of abstraction decisions with rationale.
|
||||
|
||||
## Good Abstractions (Why They Work)
|
||||
|
||||
### 1. Secp256k1Instance - Crypto Signing
|
||||
|
||||
**Location:** expect in commonMain, actual in androidMain/jvmMain/iosMain
|
||||
|
||||
**Code:**
|
||||
```kotlin
|
||||
// quartz/src/commonMain/.../Secp256k1Instance.kt
|
||||
expect object Secp256k1Instance {
|
||||
fun signSchnorr(data: ByteArray, privKey: ByteArray): ByteArray
|
||||
fun verifySchnorr(signature: ByteArray, hash: ByteArray, pubKey: ByteArray): Boolean
|
||||
}
|
||||
```
|
||||
|
||||
**Why abstracted:**
|
||||
- Used by all platforms (Android, Desktop, iOS)
|
||||
- Security APIs fundamentally different:
|
||||
- Android: secp256k1-kmp-jni-android (Android Keystore integration)
|
||||
- Desktop: secp256k1-kmp-jni-jvm (pure JVM crypto)
|
||||
- iOS: Native Security framework
|
||||
- Core protocol requirement (Nostr signatures)
|
||||
|
||||
**Decision rationale:** Always abstract crypto - varies by platform security APIs, critical for all platforms.
|
||||
|
||||
---
|
||||
|
||||
### 2. Log - Platform Logging
|
||||
|
||||
**Location:** expect object in commonMain
|
||||
|
||||
**Code:**
|
||||
```kotlin
|
||||
// quartz/src/commonMain/.../Log.kt
|
||||
expect object Log {
|
||||
fun d(tag: String, message: String)
|
||||
fun w(tag: String, message: String, throwable: Throwable?)
|
||||
fun e(tag: String, message: String, throwable: Throwable?)
|
||||
}
|
||||
```
|
||||
|
||||
**Why abstracted:**
|
||||
- Used throughout quartz module (protocol library)
|
||||
- Logging systems differ:
|
||||
- Android: android.util.Log
|
||||
- Desktop: println or logging framework
|
||||
- iOS: NSLog or OSLog
|
||||
- Simple interface, easy to implement
|
||||
|
||||
**Decision rationale:** Often abstract logging - platform systems differ, widely used, simple interface.
|
||||
|
||||
---
|
||||
|
||||
### 3. Platform Utils - Time & Platform Name
|
||||
|
||||
**Location:** expect functions in commonMain
|
||||
|
||||
**Code:**
|
||||
```kotlin
|
||||
// quartz/src/commonMain/.../Platform.kt
|
||||
expect fun platform(): String
|
||||
expect fun currentTimeSeconds(): Long
|
||||
```
|
||||
|
||||
**Why abstracted:**
|
||||
- Used by Nostr event creation (timestamps)
|
||||
- Platform name for debugging
|
||||
- Simple utilities, clear platform boundary
|
||||
|
||||
**Decision rationale:** Platform utilities are good abstraction candidates - simple, useful everywhere.
|
||||
|
||||
---
|
||||
|
||||
### 4. Jackson JSON (jvmAndroid Pattern)
|
||||
|
||||
**Location:** jvmAndroid source set
|
||||
|
||||
**Code:**
|
||||
```kotlin
|
||||
// quartz/build.gradle.kts
|
||||
val jvmAndroid = create("jvmAndroid") {
|
||||
api(libs.jackson.module.kotlin) // JVM-only library
|
||||
}
|
||||
```
|
||||
|
||||
**Why jvmAndroid (not commonMain):**
|
||||
- Jackson is JVM-specific library
|
||||
- Works on Android (JVM) + Desktop (JVM)
|
||||
- Does NOT work on iOS (not JVM) or web (not JVM)
|
||||
- Performance-critical JSON parsing
|
||||
|
||||
**Decision rationale:** Use jvmAndroid for JVM libraries shared between Android and Desktop.
|
||||
|
||||
**Future consideration:** For web support, migrate to kotlinx.serialization (works on all platforms).
|
||||
|
||||
---
|
||||
|
||||
## Bad/Over-Abstractions (Why They Failed)
|
||||
|
||||
### 1. Navigation Abstraction (Avoided)
|
||||
|
||||
**What COULD have been done:**
|
||||
```kotlin
|
||||
// ❌ Over-abstraction - DON'T DO THIS
|
||||
expect interface Navigator {
|
||||
fun navigate(route: String)
|
||||
fun popBackStack()
|
||||
}
|
||||
```
|
||||
|
||||
**Why NOT abstracted:**
|
||||
- Navigation paradigms fundamentally different:
|
||||
- Android: Activity + Compose Navigation + back stack
|
||||
- Desktop: Window + screen state + no back stack concept
|
||||
- Complex APIs don't map well
|
||||
- Creates leaky abstraction
|
||||
|
||||
**Actual approach:** Keep platform-specific
|
||||
- Android: `INav` interface + Compose Navigation
|
||||
- Desktop: Simple screen enum + state
|
||||
|
||||
**Decision rationale:** Never abstract navigation - platforms too different, abstraction would be leaky.
|
||||
|
||||
---
|
||||
|
||||
### 2. String Resources (Abstraction Planned)
|
||||
|
||||
**Current state:** Platform-specific (over-duplication)
|
||||
|
||||
**Problem:**
|
||||
```kotlin
|
||||
// Android uses R.string.*
|
||||
Text(stringResource(R.string.post_not_found))
|
||||
|
||||
// Desktop uses hardcoded strings
|
||||
Text("Post not found")
|
||||
```
|
||||
|
||||
**Why NOT yet abstracted:** Waiting for second platform to fully implement UI, then will create StringProvider interface.
|
||||
|
||||
**Planned abstraction:**
|
||||
```kotlin
|
||||
// commonMain
|
||||
interface StringProvider {
|
||||
fun get(key: String): String
|
||||
}
|
||||
|
||||
// androidMain
|
||||
class AndroidStringProvider(context: Context): StringProvider { ... }
|
||||
|
||||
// jvmMain
|
||||
class DesktopStringProvider: StringProvider { ... }
|
||||
```
|
||||
|
||||
**Lesson:** Don't abstract prematurely - wait until second platform needs it, then create proper abstraction.
|
||||
|
||||
---
|
||||
|
||||
## Platform-Specific Code (Why NOT Abstracted)
|
||||
|
||||
### 1. MainActivity (Android Activity)
|
||||
|
||||
**Location:** amethyst/src/main/.../MainActivity.kt
|
||||
|
||||
**Code:**
|
||||
```kotlin
|
||||
class MainActivity : AppCompatActivity() {
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
enableEdgeToEdge()
|
||||
setContent {
|
||||
AmethystTheme {
|
||||
AccountScreen(accountStateViewModel)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Why platform-specific:**
|
||||
- AppCompatActivity is Android framework
|
||||
- Activity lifecycle unique to Android
|
||||
- enableEdgeToEdge() is Android-specific API
|
||||
- No equivalent on Desktop (uses Window)
|
||||
|
||||
**Decision rationale:** Android Activity is platform-specific by nature.
|
||||
|
||||
---
|
||||
|
||||
### 2. Desktop Window & MenuBar
|
||||
|
||||
**Location:** desktopApp/src/jvmMain/.../Main.kt
|
||||
|
||||
**Code:**
|
||||
```kotlin
|
||||
fun main() = application {
|
||||
Window(
|
||||
onCloseRequest = ::exitApplication,
|
||||
title = "Amethyst"
|
||||
) {
|
||||
MenuBar {
|
||||
Menu("File") {
|
||||
Item("New Note", onClick = { ... }, shortcut = KeyShortcut(Key.N, ctrl = true))
|
||||
Item("Quit", onClick = ::exitApplication)
|
||||
}
|
||||
}
|
||||
NavigationRail { ... } // Sidebar navigation
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Why platform-specific:**
|
||||
- Window, MenuBar, NavigationRail are Compose Desktop APIs
|
||||
- Keyboard shortcuts (Ctrl+N) are desktop paradigm
|
||||
- Sidebar navigation vs Android bottom nav
|
||||
- No equivalent on Android
|
||||
|
||||
**Decision rationale:** Desktop UX patterns are platform-specific by nature.
|
||||
|
||||
---
|
||||
|
||||
### 3. AccountViewModel (Android ViewModel)
|
||||
|
||||
**Location:** amethyst/.../AccountStateViewModel.kt
|
||||
|
||||
**Partially abstracted:**
|
||||
- Business logic → IAccountState interface (can be shared)
|
||||
- UI state + lifecycle → AndroidX ViewModel (Android-only)
|
||||
|
||||
**Why not fully abstracted:**
|
||||
- AndroidX ViewModel lifecycle tied to Android
|
||||
- Desktop doesn't need ViewModel (simpler state management)
|
||||
- SavedStateHandle is Android-specific
|
||||
|
||||
**Decision rationale:** Extract business logic to interface, keep UI state platform-specific.
|
||||
|
||||
---
|
||||
|
||||
## Migration Examples (Android → Shared)
|
||||
|
||||
### Example 1: PubKeyFormatter (Pure Kotlin)
|
||||
|
||||
**Before:**
|
||||
```kotlin
|
||||
// amethyst/ui/note/PubKeyFormatter.kt
|
||||
fun String.toDisplayHexKey(): String {
|
||||
return "${take(8)}:${takeLast(8)}"
|
||||
}
|
||||
```
|
||||
|
||||
**After:**
|
||||
```kotlin
|
||||
// commons/commonMain/formatters/PubKeyFormatter.kt
|
||||
fun String.toDisplayHexKey(): String {
|
||||
return "${take(8)}:${takeLast(8)}"
|
||||
}
|
||||
|
||||
// Both apps use it
|
||||
import com.vitorpamplona.amethyst.commons.formatters.toDisplayHexKey
|
||||
```
|
||||
|
||||
**Why successful:**
|
||||
- Pure Kotlin, no platform dependencies
|
||||
- Widely reused
|
||||
- Simple utility function
|
||||
|
||||
---
|
||||
|
||||
### Example 2: TimeAgoFormatter (Requires Abstraction)
|
||||
|
||||
**Problem:**
|
||||
```kotlin
|
||||
// Uses Android R.string.*
|
||||
fun timeAgo(timestamp: Long): String {
|
||||
return context.getString(R.string.x_minutes_ago, minutes)
|
||||
}
|
||||
```
|
||||
|
||||
**Solution:** Abstract string resources
|
||||
```kotlin
|
||||
// commonMain
|
||||
fun timeAgo(timestamp: Long, stringProvider: StringProvider): String {
|
||||
return stringProvider.get("x_minutes_ago", minutes)
|
||||
}
|
||||
|
||||
// androidMain
|
||||
stringProvider = AndroidStringProvider(context)
|
||||
|
||||
// jvmMain
|
||||
stringProvider = DesktopStringProvider()
|
||||
```
|
||||
|
||||
**Why successful:** Clear platform boundary (string resources), useful on both platforms.
|
||||
|
||||
---
|
||||
|
||||
## Decision Pattern Summary
|
||||
|
||||
| Pattern | Abstract? | Why |
|
||||
|---------|-----------|-----|
|
||||
| Pure Kotlin utilities | ✅ YES | No platform dependency, easy |
|
||||
| Crypto APIs | ✅ YES (expect/actual) | Platform security APIs differ |
|
||||
| JVM libraries | ⚠️ jvmAndroid | Works on Android+Desktop only |
|
||||
| UI components (simple) | ✅ YES | Composables work cross-platform |
|
||||
| UI components (complex) | ❌ NO | Platform dependencies |
|
||||
| Navigation | ❌ NO | Paradigms too different |
|
||||
| ViewModels | ⚠️ PARTIAL | Business logic yes, UI state no |
|
||||
| String resources | ⚠️ PLANNED | Needs abstraction layer |
|
||||
@@ -0,0 +1,163 @@
|
||||
# Complete expect/actual Catalog
|
||||
|
||||
All 24 expect declarations in Amethyst quartz module with rationale.
|
||||
|
||||
| # | Name | Type | Purpose | Why Abstracted | Files |
|
||||
|---|------|------|---------|----------------|-------|
|
||||
| 1 | AESCBC | class | AES CBC encryption | Platform crypto APIs differ | quartz/.../ciphers/AESCBC.kt |
|
||||
| 2 | AESGCM | class | AES GCM encryption | Platform crypto APIs differ | quartz/.../ciphers/AESGCM.kt |
|
||||
| 3 | DigestInstance | class | Hash digests (SHA256) | Platform implementations | quartz/.../diggest/DigestInstance.kt |
|
||||
| 4 | MacInstance | class | MAC (HMAC) operations | Platform crypto APIs | quartz/.../mac/MacInstance.kt |
|
||||
| 5 | Sha256 | object | SHA256 hashing | Platform-specific optimizations | quartz/.../sha256/Sha256.kt |
|
||||
| 6 | LargeCache | object | Large object caching | Platform storage APIs differ | quartz/.../cache/LargeCache.kt |
|
||||
| 7 | UriParser | object | URI parsing | Platform URL APIs differ | quartz/.../UriParser.kt |
|
||||
| 8 | UrlEncoder | object | URL encoding | Platform encoding differs | quartz/.../UrlEncoder.kt |
|
||||
| 9 | Urls | object | URL utilities | Platform URL handling | quartz/.../Urls.kt |
|
||||
| 10 | Platform | functions | platform(), currentTimeSeconds() | Platform name & time APIs | quartz/.../Platform.kt |
|
||||
| 11 | Rfc3986 | object | RFC 3986 URL normalization | Used in jvmAndroid | quartz/.../Rfc3986.kt |
|
||||
| 12 | Secp256k1Instance | object | Bitcoin crypto (secp256k1) | Different libs per platform | quartz/.../Secp256k1Instance.kt |
|
||||
| 13 | SecureRandom | object | Cryptographically secure random | Platform random APIs differ | quartz/.../SecureRandom.kt |
|
||||
| 14 | StringExt | functions | String utilities | Platform string handling | quartz/.../StringExt.kt |
|
||||
| 15 | UnicodeNormalizer | object | Unicode normalization | Platform text APIs | quartz/.../UnicodeNormalizer.kt |
|
||||
| 16 | GZip | object | GZip compression | Platform compression APIs | quartz/.../GZip.kt |
|
||||
| 17 | LibSodiumInstance | object | NaCl/libsodium (NIP-44 encryption) | Different libs per platform | quartz/.../LibSodiumInstance.kt |
|
||||
| 18 | Log | object | Logging | Platform logging systems | quartz/.../Log.kt |
|
||||
| 19 | BigDecimal | class | Arbitrary precision decimal | Not in Kotlin common stdlib | quartz/.../BigDecimal.kt |
|
||||
| 20 | BitSet | class | Bit set data structure | Not in Kotlin common stdlib | quartz/.../BitSet.kt |
|
||||
| 21 | ServerInfoParser | object | Server info parsing (NIP-96) | Platform JSON parsing | quartz/.../nip96.../ServerInfoParser.kt |
|
||||
| 22 | EventHasherSerializer | object | Event hashing | Platform-specific optimizations | quartz/.../nip01Core.../EventHasherSerializer.kt |
|
||||
| 23 | OptimizedJsonMapper | object | JSON mapping | Platform JSON libraries | quartz/.../nip01Core.../OptimizedJsonMapper.kt |
|
||||
| 24 | Address | data class | Address data structure | Platform-specific string handling | quartz/.../nip01Core.../Address.kt |
|
||||
|
||||
## Pattern Analysis
|
||||
|
||||
### Objects (Singletons) - 19 total
|
||||
Most common pattern for platform-specific singletons:
|
||||
- Crypto: Secp256k1Instance, LibSodiumInstance, Sha256
|
||||
- I/O: UriParser, UrlEncoder, GZip
|
||||
- Utils: Log, Platform, SecureRandom
|
||||
|
||||
### Classes (Instantiable) - 4 total
|
||||
For objects that need to maintain state:
|
||||
- AESCBC, AESGCM (cipher state)
|
||||
- DigestInstance, MacInstance (hash/MAC state)
|
||||
- BigDecimal, BitSet (data structures)
|
||||
|
||||
### Functions - 2 total
|
||||
Simple utilities:
|
||||
- platform(), currentTimeSeconds()
|
||||
|
||||
## Why Abstracted Categories
|
||||
|
||||
### Crypto (8 items)
|
||||
**Always abstract:** Security APIs fundamentally different across platforms
|
||||
- Android: Android Keystore, secp256k1-android
|
||||
- Desktop: JVM crypto, secp256k1-jvm
|
||||
- iOS: Security framework, native crypto
|
||||
|
||||
### I/O & Platform Utils (7 items)
|
||||
**Often abstract:** File systems, URLs, compression differ
|
||||
- Platform storage APIs
|
||||
- URL handling varies
|
||||
- Compression libraries differ
|
||||
|
||||
### Data Structures (2 items)
|
||||
**Abstract when missing:** Not available in Kotlin common stdlib
|
||||
- BigDecimal, BitSet not in common
|
||||
|
||||
### JSON/Parsing (3 items)
|
||||
**Platform-specific optimization:** Uses platform JSON libraries
|
||||
- Android/Desktop: Jackson (via jvmAndroid)
|
||||
- iOS: Native parsers
|
||||
|
||||
### Logging (1 item)
|
||||
**Always abstract:** Platform logging systems differ
|
||||
- Android: android.util.Log
|
||||
- Desktop: println or logging framework
|
||||
- iOS: NSLog or OSLog
|
||||
|
||||
## Actual Implementation Examples
|
||||
|
||||
### Simple Object Pattern
|
||||
|
||||
```kotlin
|
||||
// commonMain
|
||||
expect object Log {
|
||||
fun d(tag: String, message: String)
|
||||
}
|
||||
|
||||
// androidMain
|
||||
actual object Log {
|
||||
actual fun d(tag: String, message: String) {
|
||||
android.util.Log.d(tag, message)
|
||||
}
|
||||
}
|
||||
|
||||
// jvmMain
|
||||
actual object Log {
|
||||
actual fun d(tag: String, message: String) {
|
||||
println("[$tag] $message")
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Complex Object with Dependencies
|
||||
|
||||
```kotlin
|
||||
// commonMain
|
||||
expect object Secp256k1Instance {
|
||||
fun signSchnorr(data: ByteArray, privKey: ByteArray): ByteArray
|
||||
}
|
||||
|
||||
// androidMain - uses JNI bindings
|
||||
actual object Secp256k1Instance {
|
||||
actual fun signSchnorr(data: ByteArray, privKey: ByteArray): ByteArray {
|
||||
return fr.acinq.secp256k1.Secp256k1.signSchnorr(data, privKey, null)
|
||||
}
|
||||
}
|
||||
|
||||
// jvmMain - different JNI library
|
||||
actual object Secp256k1Instance {
|
||||
actual fun signSchnorr(data: ByteArray, privKey: ByteArray): ByteArray {
|
||||
return fr.acinq.secp256k1.Secp256k1.signSchnorr(data, privKey, null)
|
||||
}
|
||||
}
|
||||
|
||||
// iosMain - native iOS implementation
|
||||
actual object Secp256k1Instance {
|
||||
actual fun signSchnorr(data: ByteArray, privKey: ByteArray): ByteArray {
|
||||
// Uses iOS Security framework or native lib
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Class Pattern
|
||||
|
||||
```kotlin
|
||||
// commonMain
|
||||
expect class BigDecimal {
|
||||
constructor(value: String)
|
||||
fun add(other: BigDecimal): BigDecimal
|
||||
override fun toString(): String
|
||||
}
|
||||
|
||||
// jvmAndroid (works on Android + Desktop)
|
||||
actual typealias BigDecimal = java.math.BigDecimal
|
||||
|
||||
// iosMain
|
||||
actual class BigDecimal {
|
||||
private val value: NSDecimalNumber
|
||||
actual constructor(value: String) {
|
||||
this.value = NSDecimalNumber(value)
|
||||
}
|
||||
// ... implementation
|
||||
}
|
||||
```
|
||||
|
||||
## Decision Patterns
|
||||
|
||||
Ask for each declaration:
|
||||
1. **Used by 2+ platforms?** → YES (otherwise platform-specific)
|
||||
2. **Pure Kotlin possible?** → NO (otherwise commonMain)
|
||||
3. **Varies by platform?** → YES (expect/actual)
|
||||
4. **JVM-only library?** → NO (otherwise jvmAndroid)
|
||||
@@ -0,0 +1,332 @@
|
||||
# Source Set Hierarchy in Amethyst
|
||||
|
||||
Visual guide to source set organization with concrete examples from the codebase.
|
||||
|
||||
## Hierarchy Diagram
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ commonMain │
|
||||
│ Pure Kotlin, no platform APIs │
|
||||
│ Examples: │
|
||||
│ - Nostr event parsing (TextNoteEvent, MetadataEvent) │
|
||||
│ - Business logic (data validation, crypto algorithms) │
|
||||
│ - Data models (@Immutable data classes) │
|
||||
│ Dependencies: kotlin-stdlib, kotlinx-coroutines │
|
||||
└──────────────────────┬──────────────────────────────────────┘
|
||||
│
|
||||
┌────────────┴────────────┬───────────────┐
|
||||
│ │ │
|
||||
▼ ▼ ▼
|
||||
┌──────────────────┐ ┌───────────────────┐ ┌──────────────┐
|
||||
│ jvmAndroid │ │ iosMain │ │ Future: │
|
||||
│ JVM libraries │ │ iOS common │ │ jsMain │
|
||||
│ Examples: │ │ Examples: │ │ wasmMain │
|
||||
│ - Jackson JSON │ │ - Platform API │ └──────────────┘
|
||||
│ - OkHttp HTTP │ │ - Actuals for │
|
||||
│ - url-detector │ │ crypto/I/O │
|
||||
│ Dependencies: │ │ Dependencies: │
|
||||
│ - Jackson │ │ - Platform libs │
|
||||
│ - OkHttp │ └───────┬───────────┘
|
||||
└────┬─────────┬───┘ │
|
||||
│ │ ├─→ iosX64Main (simulator Intel)
|
||||
│ │ ├─→ iosArm64Main (device ARM64)
|
||||
│ │ └─→ iosSimulatorArm64Main (Apple Silicon)
|
||||
▼ ▼
|
||||
┌──────────┐ ┌───────────┐
|
||||
│android │ │ jvmMain │
|
||||
│Main │ │ (Desktop) │
|
||||
│Examples: │ │ Examples: │
|
||||
│- Activity│ │- Window │
|
||||
│- ViewModel│ │- MenuBar │
|
||||
│- Android │ │- Desktop │
|
||||
│ APIs │ │ Compose │
|
||||
│Deps: │ │ Deps: │
|
||||
│- secp256k│ │- secp256k │
|
||||
│ 1-android│ │ 1-jvm │
|
||||
│- androidx│ │- Compose │
|
||||
│ │ │ Desktop │
|
||||
└──────────┘ └───────────┘
|
||||
```
|
||||
|
||||
## Dependency Flow
|
||||
|
||||
```
|
||||
Code in commonMain
|
||||
↓ can use
|
||||
Nothing (only Kotlin stdlib)
|
||||
|
||||
Code in jvmAndroid
|
||||
↓ can use
|
||||
commonMain + JVM libraries (Jackson, OkHttp)
|
||||
|
||||
Code in androidMain
|
||||
↓ can use
|
||||
commonMain + jvmAndroid + Android framework
|
||||
|
||||
Code in jvmMain
|
||||
↓ can use
|
||||
commonMain + jvmAndroid + JVM + Compose Desktop
|
||||
|
||||
Code in iosMain
|
||||
↓ can use
|
||||
commonMain + iOS platform APIs
|
||||
```
|
||||
|
||||
## Real Examples from Amethyst
|
||||
|
||||
### commonMain - Pure Kotlin
|
||||
|
||||
**File:** `quartz/src/commonMain/.../TextNoteEvent.kt`
|
||||
|
||||
```kotlin
|
||||
@Immutable
|
||||
class TextNoteEvent(
|
||||
id: HexKey,
|
||||
pubKey: HexKey,
|
||||
createdAt: Long,
|
||||
tags: Array<Array<String>>,
|
||||
content: String,
|
||||
sig: HexKey,
|
||||
) : BaseThreadedEvent(...) {
|
||||
// Pure Kotlin - works everywhere
|
||||
override fun indexableContent() = "Subject: " + subject() + "\n" + content
|
||||
}
|
||||
```
|
||||
|
||||
**Why commonMain:**
|
||||
- Pure Kotlin code
|
||||
- No platform APIs
|
||||
- Data class with business logic
|
||||
- Needed by all platforms
|
||||
|
||||
---
|
||||
|
||||
### jvmAndroid - JVM Libraries
|
||||
|
||||
**File:** `quartz/build.gradle.kts`
|
||||
|
||||
```kotlin
|
||||
val jvmAndroid = create("jvmAndroid") {
|
||||
dependsOn(commonMain.get())
|
||||
|
||||
dependencies {
|
||||
// Normalizes URLs
|
||||
api(libs.rfc3986.normalizer)
|
||||
|
||||
// Performant Parser of JSONs into Events
|
||||
api(libs.jackson.module.kotlin)
|
||||
|
||||
// Parses URLs from Text
|
||||
api(libs.url.detector)
|
||||
|
||||
// Websockets API
|
||||
implementation(libs.okhttp)
|
||||
implementation(libs.okhttpCoroutines)
|
||||
}
|
||||
}
|
||||
|
||||
jvmMain { dependsOn(jvmAndroid) } // Desktop gets Jackson, OkHttp
|
||||
androidMain { dependsOn(jvmAndroid) } // Android gets Jackson, OkHttp
|
||||
```
|
||||
|
||||
**Why jvmAndroid:**
|
||||
- Jackson, OkHttp are JVM-only libraries
|
||||
- Works on Android (JVM) and Desktop (JVM)
|
||||
- Does NOT work on iOS (not JVM) or web (not JVM)
|
||||
|
||||
**Usage in code:**
|
||||
```kotlin
|
||||
// Can use Jackson in jvmAndroid source set
|
||||
val mapper = ObjectMapper()
|
||||
val event = mapper.readValue(json, Event::class.java)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### androidMain - Android Platform
|
||||
|
||||
**File:** `amethyst/src/main/.../MainActivity.kt`
|
||||
|
||||
```kotlin
|
||||
class MainActivity : AppCompatActivity() {
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
enableEdgeToEdge() // Android API
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
setContent { // Compose for Android
|
||||
AmethystTheme {
|
||||
val accountStateViewModel: AccountStateViewModel = viewModel()
|
||||
AccountScreen(accountStateViewModel)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Why androidMain:**
|
||||
- AppCompatActivity is Android framework
|
||||
- Activity lifecycle Android-specific
|
||||
- AndroidX libraries (viewModel())
|
||||
|
||||
**Dependencies:**
|
||||
```kotlin
|
||||
androidMain {
|
||||
dependsOn(jvmAndroid) // Gets Jackson, OkHttp
|
||||
dependencies {
|
||||
implementation(libs.androidx.core.ktx)
|
||||
api(libs.secp256k1.kmp.jni.android) // Android crypto
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### jvmMain - Desktop Platform
|
||||
|
||||
**File:** `desktopApp/src/jvmMain/.../Main.kt`
|
||||
|
||||
```kotlin
|
||||
fun main() = application {
|
||||
val windowState = rememberWindowState(
|
||||
width = 1200.dp,
|
||||
height = 800.dp
|
||||
)
|
||||
|
||||
Window( // Compose Desktop API
|
||||
onCloseRequest = ::exitApplication,
|
||||
state = windowState,
|
||||
title = "Amethyst"
|
||||
) {
|
||||
MenuBar { // Desktop-specific
|
||||
Menu("File") {
|
||||
Item("New Note", shortcut = KeyShortcut(Key.N, ctrl = true))
|
||||
}
|
||||
}
|
||||
NavigationRail { ... } // Sidebar
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Why jvmMain:**
|
||||
- Window, MenuBar, NavigationRail are Compose Desktop
|
||||
- Keyboard shortcuts desktop paradigm
|
||||
- Different UX from Android (sidebar vs bottom nav)
|
||||
|
||||
**Dependencies:**
|
||||
```kotlin
|
||||
jvmMain {
|
||||
dependsOn(jvmAndroid) // Gets Jackson, OkHttp
|
||||
dependencies {
|
||||
implementation(libs.secp256k1.kmp.jni.jvm) // Desktop crypto
|
||||
implementation(compose.desktop.currentOs)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### iosMain - iOS Platform
|
||||
|
||||
**File:** `quartz/build.gradle.kts`
|
||||
|
||||
```kotlin
|
||||
iosMain {
|
||||
dependsOn(commonMain.get())
|
||||
dependencies {
|
||||
// iOS platform dependencies
|
||||
}
|
||||
}
|
||||
|
||||
val iosX64Main by getting { dependsOn(iosMain.get()) }
|
||||
val iosArm64Main by getting { dependsOn(iosMain.get()) }
|
||||
val iosSimulatorArm64Main by getting { dependsOn(iosMain.get()) }
|
||||
```
|
||||
|
||||
**Why iosMain:**
|
||||
- iOS platform APIs
|
||||
- Native crypto (Security framework)
|
||||
- Different from Android/Desktop
|
||||
|
||||
**Architecture targets:**
|
||||
- iosX64Main: Intel simulator
|
||||
- iosArm64Main: Device (iPhone, iPad)
|
||||
- iosSimulatorArm64Main: Apple Silicon simulator
|
||||
|
||||
---
|
||||
|
||||
## Build Order Matters
|
||||
|
||||
**CRITICAL:** jvmAndroid must be defined BEFORE androidMain and jvmMain:
|
||||
|
||||
```kotlin
|
||||
// ✅ CORRECT ORDER
|
||||
val jvmAndroid = create("jvmAndroid") { ... }
|
||||
jvmMain { dependsOn(jvmAndroid) }
|
||||
androidMain { dependsOn(jvmAndroid) }
|
||||
|
||||
// ❌ WRONG - Build error
|
||||
androidMain { dependsOn(jvmAndroid) } // jvmAndroid not defined yet!
|
||||
val jvmAndroid = create("jvmAndroid") { ... }
|
||||
```
|
||||
|
||||
See comment in quartz/build.gradle.kts:131:
|
||||
```kotlin
|
||||
// Must be defined before androidMain and jvmMain
|
||||
val jvmAndroid = create("jvmAndroid") { ... }
|
||||
```
|
||||
|
||||
## Choosing the Right Source Set
|
||||
|
||||
Decision flowchart:
|
||||
|
||||
```
|
||||
Q: Where should this code go?
|
||||
|
||||
├─ Pure Kotlin? (no platform APIs)
|
||||
│ └─ commonMain
|
||||
│
|
||||
├─ JVM library? (Jackson, OkHttp)
|
||||
│ └─ jvmAndroid
|
||||
│
|
||||
├─ Android API? (Activity, Context)
|
||||
│ └─ androidMain
|
||||
│
|
||||
├─ Desktop API? (Window, MenuBar)
|
||||
│ └─ jvmMain
|
||||
│
|
||||
└─ iOS API? (platform.posix, Security)
|
||||
└─ iosMain
|
||||
```
|
||||
|
||||
## Future: Web/wasm Source Sets
|
||||
|
||||
**Not yet implemented**, but structure would be:
|
||||
|
||||
```
|
||||
commonMain
|
||||
├─→ jsMain (JavaScript/Web)
|
||||
│ └─ JS-specific: DOM APIs, fetch
|
||||
│
|
||||
└─→ wasmMain (WebAssembly)
|
||||
└─ wasm-specific: limited APIs
|
||||
```
|
||||
|
||||
**Constraints:**
|
||||
- Cannot use jvmAndroid (Jackson, OkHttp)
|
||||
- Cannot use platform.posix
|
||||
- Must use pure Kotlin or web-compatible libs (ktor, kotlinx.serialization)
|
||||
|
||||
## Summary Table
|
||||
|
||||
| Source Set | Extends | Can Use | Example Code |
|
||||
|------------|---------|---------|--------------|
|
||||
| commonMain | - | Kotlin stdlib only | TextNoteEvent, business logic |
|
||||
| jvmAndroid | commonMain | JVM libs (Jackson, OkHttp) | JSON parsing, HTTP |
|
||||
| androidMain | jvmAndroid | Android framework | Activity, ViewModel |
|
||||
| jvmMain | jvmAndroid | JVM + Compose Desktop | Window, MenuBar |
|
||||
| iosMain | commonMain | iOS platform | Security framework |
|
||||
| iosX64Main | iosMain | Simulator (Intel) | Architecture-specific |
|
||||
| iosArm64Main | iosMain | Device (ARM64) | Architecture-specific |
|
||||
| jsMain | commonMain | JS/DOM | Web (future) |
|
||||
| wasmMain | commonMain | wasm APIs | WebAssembly (future) |
|
||||
@@ -0,0 +1,345 @@
|
||||
# Target Compatibility Guide
|
||||
|
||||
Current targets (Android, JVM/Desktop, iOS) and future targets (web, wasm) with constraints.
|
||||
|
||||
## Current Primary Targets
|
||||
|
||||
### Android (androidMain)
|
||||
|
||||
**Status:** ✅ Mature, production-ready
|
||||
|
||||
**Runtime:** JVM (Dalvik/ART)
|
||||
|
||||
**Available:**
|
||||
- Android framework (Activity, Context, Intent, etc.)
|
||||
- AndroidX libraries (ViewModel, Navigation, etc.)
|
||||
- JVM libraries via jvmAndroid (Jackson, OkHttp)
|
||||
- Platform-specific crypto: secp256k1-kmp-jni-android
|
||||
|
||||
**Constraints:**
|
||||
- Mobile UX paradigms (bottom navigation, vertical scroll)
|
||||
- Touch-first interaction
|
||||
- Limited screen space
|
||||
- Battery/performance constraints
|
||||
|
||||
**Example code:**
|
||||
```kotlin
|
||||
// androidMain
|
||||
class MainActivity : AppCompatActivity() {
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
// Android-specific lifecycle
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### JVM / Desktop (jvmMain)
|
||||
|
||||
**Status:** ✅ Active development, functional
|
||||
|
||||
**Runtime:** JVM
|
||||
|
||||
**Available:**
|
||||
- Pure JVM libraries
|
||||
- JVM libraries via jvmAndroid (Jackson, OkHttp)
|
||||
- Compose Desktop (Window, MenuBar, etc.)
|
||||
- Platform-specific crypto: secp256k1-kmp-jni-jvm
|
||||
|
||||
**Constraints:**
|
||||
- Desktop UX paradigms (sidebar, menus, keyboard shortcuts)
|
||||
- Keyboard + mouse interaction
|
||||
- Larger screen space
|
||||
- Different navigation patterns (no back stack)
|
||||
|
||||
**Example code:**
|
||||
```kotlin
|
||||
// jvmMain
|
||||
fun main() = application {
|
||||
Window(
|
||||
onCloseRequest = ::exitApplication,
|
||||
title = "Amethyst"
|
||||
) {
|
||||
MenuBar { ... } // Desktop-specific
|
||||
NavigationRail { ... } // Sidebar
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### iOS (iosMain + architecture targets)
|
||||
|
||||
**Status:** ⚠️ In development, framework configured
|
||||
|
||||
**Runtime:** Native iOS
|
||||
|
||||
**Source sets:**
|
||||
- iosMain (common iOS code)
|
||||
- iosX64Main (Intel simulator)
|
||||
- iosArm64Main (device - iPhone/iPad)
|
||||
- iosSimulatorArm64Main (Apple Silicon simulator)
|
||||
|
||||
**Available:**
|
||||
- iOS platform APIs (platform.posix, Foundation, etc.)
|
||||
- Native crypto (Security framework)
|
||||
- SwiftUI integration (via KMP framework)
|
||||
|
||||
**NOT available:**
|
||||
- JVM libraries (Jackson, OkHttp)
|
||||
- jvmAndroid source set
|
||||
- JVM-specific APIs
|
||||
|
||||
**Constraints:**
|
||||
- Mobile UX (similar to Android)
|
||||
- Swift/Objective-C interop
|
||||
- XCFramework distribution
|
||||
- CocoaPods integration
|
||||
|
||||
**Example code:**
|
||||
```kotlin
|
||||
// iosMain
|
||||
actual object Secp256k1Instance {
|
||||
actual fun signSchnorr(...): ByteArray {
|
||||
// Use iOS Security framework
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**XCFramework setup:**
|
||||
```kotlin
|
||||
// quartz/build.gradle.kts
|
||||
kotlin {
|
||||
listOf(iosX64(), iosArm64(), iosSimulatorArm64())
|
||||
.forEach { target ->
|
||||
target.binaries.framework {
|
||||
baseName = "quartz-kmpKit"
|
||||
isStatic = true
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Future Targets
|
||||
|
||||
### Web / JavaScript (jsMain)
|
||||
|
||||
**Status:** ❌ Not implemented, consider for future
|
||||
|
||||
**Runtime:** JavaScript (browser or Node.js)
|
||||
|
||||
**Available:**
|
||||
- Kotlin/JS stdlib
|
||||
- JS/DOM APIs
|
||||
- kotlinx.* libraries (serialization, coroutines, datetime)
|
||||
- ktor-client (HTTP)
|
||||
|
||||
**NOT available:**
|
||||
- ❌ JVM libraries (Jackson, OkHttp)
|
||||
- ❌ jvmAndroid source set
|
||||
- ❌ platform.posix (no file system access like native)
|
||||
- ❌ Blocking APIs (different async model - JS event loop)
|
||||
|
||||
**Constraints:**
|
||||
- Single-threaded event loop
|
||||
- No blocking calls
|
||||
- Different async patterns (Promises, async/await)
|
||||
- Browser security (CORS, no file system)
|
||||
|
||||
**Migration path from current code:**
|
||||
|
||||
| Current (jvmAndroid) | Web-compatible alternative |
|
||||
|---------------------|---------------------------|
|
||||
| Jackson JSON | kotlinx.serialization |
|
||||
| OkHttp HTTP | ktor-client |
|
||||
| java.math.BigDecimal | Kotlin BigDecimal (coming) |
|
||||
| Blocking I/O | Suspending functions |
|
||||
|
||||
**Example migration:**
|
||||
```kotlin
|
||||
// Current: jvmAndroid
|
||||
val mapper = ObjectMapper()
|
||||
val event = mapper.readValue(json, Event::class.java)
|
||||
|
||||
// Future: commonMain (works on web)
|
||||
val json = Json { ignoreUnknownKeys = true }
|
||||
val event = json.decodeFromString<Event>(jsonString)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### WebAssembly (wasmMain)
|
||||
|
||||
**Status:** ❌ Not implemented, experimental Kotlin/Wasm
|
||||
|
||||
**Runtime:** WebAssembly
|
||||
|
||||
**Available:**
|
||||
- Kotlin/Wasm stdlib
|
||||
- Limited kotlinx.* libraries
|
||||
- wasm-specific APIs
|
||||
|
||||
**NOT available:**
|
||||
- ❌ JVM libraries
|
||||
- ❌ Full platform.posix
|
||||
- ❌ Many kotlinx libraries (limited wasm support)
|
||||
|
||||
**Constraints:**
|
||||
- Even more limited than JS
|
||||
- Experimental Kotlin support
|
||||
- Limited library ecosystem
|
||||
|
||||
**Recommendation:** Focus on web (jsMain) first, wasm later.
|
||||
|
||||
---
|
||||
|
||||
## Cross-Target Compatibility Matrix
|
||||
|
||||
| Feature | Android | JVM/Desktop | iOS | Web (JS) | wasm |
|
||||
|---------|---------|-------------|-----|----------|------|
|
||||
| Pure Kotlin | ✅ | ✅ | ✅ | ✅ | ✅ |
|
||||
| kotlinx.coroutines | ✅ | ✅ | ✅ | ✅ | ⚠️ |
|
||||
| kotlinx.serialization | ✅ | ✅ | ✅ | ✅ | ⚠️ |
|
||||
| kotlinx.datetime | ✅ | ✅ | ✅ | ✅ | ⚠️ |
|
||||
| ktor-client | ✅ | ✅ | ✅ | ✅ | ❌ |
|
||||
| Jackson JSON | ✅ (jvmAndroid) | ✅ (jvmAndroid) | ❌ | ❌ | ❌ |
|
||||
| OkHttp | ✅ (jvmAndroid) | ✅ (jvmAndroid) | ❌ | ❌ | ❌ |
|
||||
| platform.posix | ❌ | ❌ | ✅ | ❌ | ⚠️ |
|
||||
| Compose Multiplatform | ✅ | ✅ | ⚠️ (experimental) | ⚠️ (experimental) | ❌ |
|
||||
|
||||
Legend:
|
||||
- ✅ Full support
|
||||
- ⚠️ Limited/experimental
|
||||
- ❌ Not available
|
||||
|
||||
---
|
||||
|
||||
## Future-Proofing Recommendations
|
||||
|
||||
### For Web Compatibility
|
||||
|
||||
**DO:**
|
||||
- ✅ Use kotlinx.serialization instead of Jackson
|
||||
- ✅ Use ktor-client instead of OkHttp
|
||||
- ✅ Use kotlinx.datetime instead of java.time
|
||||
- ✅ Use suspending functions (non-blocking)
|
||||
- ✅ Keep business logic in commonMain
|
||||
|
||||
**DON'T:**
|
||||
- ❌ Put JVM libraries in commonMain
|
||||
- ❌ Use platform.posix for critical features
|
||||
- ❌ Use blocking I/O
|
||||
- ❌ Depend on threading (use coroutines)
|
||||
|
||||
**Example:**
|
||||
```kotlin
|
||||
// ❌ NOT web-compatible
|
||||
// jvmAndroid
|
||||
fun parseJson(json: String): Event {
|
||||
val mapper = ObjectMapper() // Jackson - JVM only
|
||||
return mapper.readValue(json, Event::class.java)
|
||||
}
|
||||
|
||||
// ✅ Web-compatible
|
||||
// commonMain
|
||||
@Serializable
|
||||
data class Event(...)
|
||||
|
||||
fun parseJson(json: String): Event {
|
||||
return Json.decodeFromString<Event>(json) // Works everywhere
|
||||
}
|
||||
```
|
||||
|
||||
### Current Migration Priorities
|
||||
|
||||
**High priority:** (Needed for web)
|
||||
1. Migrate Jackson → kotlinx.serialization
|
||||
2. Migrate OkHttp → ktor-client
|
||||
3. Move business logic to commonMain
|
||||
|
||||
**Medium priority:** (Nice to have)
|
||||
1. Abstract date/time handling → kotlinx.datetime
|
||||
2. Remove platform.posix usage where possible
|
||||
3. Use suspending functions over blocking
|
||||
|
||||
**Low priority:** (Future optimization)
|
||||
1. wasm-specific optimizations
|
||||
2. Platform-specific performance tuning
|
||||
|
||||
---
|
||||
|
||||
## Platform-Specific Patterns
|
||||
|
||||
### Android vs iOS Differences
|
||||
|
||||
| Aspect | Android | iOS |
|
||||
|--------|---------|-----|
|
||||
| **Activity/ViewController** | Activity | UIViewController |
|
||||
| **Navigation** | Compose Navigation | UINavigationController |
|
||||
| **Lifecycle** | onCreate, onResume, etc. | viewDidLoad, viewWillAppear |
|
||||
| **Permissions** | Runtime permissions | Info.plist + runtime |
|
||||
| **Crypto** | secp256k1-android | Security framework |
|
||||
| **Storage** | Room, SharedPreferences | Core Data, UserDefaults |
|
||||
|
||||
### Desktop vs Mobile Differences
|
||||
|
||||
| Aspect | Desktop | Mobile |
|
||||
|--------|---------|--------|
|
||||
| **Navigation** | Sidebar | Bottom nav |
|
||||
| **Input** | Keyboard + mouse | Touch |
|
||||
| **Screen** | Large, landscape | Small, portrait |
|
||||
| **Windows** | Multi-window | Single app |
|
||||
| **Shortcuts** | Keyboard shortcuts (Ctrl+N) | None |
|
||||
| **Menus** | MenuBar | Bottom sheets |
|
||||
|
||||
---
|
||||
|
||||
## Testing Strategy
|
||||
|
||||
### Per-Target Testing
|
||||
|
||||
**Android:**
|
||||
- Unit tests: androidTest
|
||||
- Instrumented: androidInstrumentedTest
|
||||
- Device/emulator testing
|
||||
|
||||
**Desktop:**
|
||||
- Unit tests: jvmTest
|
||||
- Manual desktop app testing
|
||||
|
||||
**iOS:**
|
||||
- Unit tests: iosTest (iosX64Test, iosArm64Test, etc.)
|
||||
- Simulator/device testing
|
||||
|
||||
**Web (future):**
|
||||
- Unit tests: jsTest
|
||||
- Browser testing (Selenium, Playwright)
|
||||
|
||||
### Shared Testing
|
||||
|
||||
**commonTest:**
|
||||
- Business logic tests
|
||||
- Pure Kotlin code
|
||||
- Works on all platforms
|
||||
|
||||
```kotlin
|
||||
// commonTest
|
||||
class EventParsingTest {
|
||||
@Test
|
||||
fun parseTextNoteEvent() {
|
||||
// Tests run on all platforms
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Summary
|
||||
|
||||
**Current Focus:** Android, JVM/Desktop, iOS (active development)
|
||||
|
||||
**Future Considerations:** Web (requires migration from Jackson/OkHttp)
|
||||
|
||||
**Key Decision:** Prefer kotlinx.* libraries over JVM-specific libs for future web compatibility.
|
||||
Reference in New Issue
Block a user