Files
amethyst/AGENTS.md
Nick Pulido 6cee6540ea
Create Release Assets / build-desktop (arm64, macos, macos-14, packageReleaseDmg) (push) Has been cancelled
Create Release Assets / build-desktop (x64, linux, ubuntu-latest, packageReleaseDeb packageReleaseRpm) (push) Has been cancelled
Create Release Assets / build-desktop (x64, linux-portable, ubuntu-latest, createReleaseAppImage createReleaseDistributable) (push) Has been cancelled
Create Release Assets / build-desktop (x64, windows, windows-latest, packageReleaseMsi createReleaseDistributable) (push) Has been cancelled
Create Release Assets / build-cli (arm64, macos, macos-14, amyImage) (push) Has been cancelled
Create Release Assets / build-cli (x64, linux, ubuntu-latest, amyImage jpackageDeb jpackageRpm) (push) Has been cancelled
Create Release Assets / deploy-android (push) Has been cancelled
feat: Add MoneroScreen UI and Account reactive flows
- MoneroScreen.kt: complete ~800-line UI with send, receive,
  daemon settings, transaction list, backup seed dialog
- Account.kt: add 7 reactive StateFlows and updateMoneroState()
- strings.xml: add monero-related string resources
- AGENTS.md: update with actual progress state
- GARNET_README.md: rewrite merge progress section
- Uses MaterialSymbols from commons, no compose-material-icons-extended
- compileFdroidDebugKotlin: 0 errors
2026-05-22 18:22:11 -07:00

25 KiB

Garnet Integration into Amethyst - Project Notes & Learnings

Quick Reference

Item Value
Garnet repo https://github.com/retrnull/garnet
Amethyst repo https://github.com/vitorpamplona/amethyst
Garnet base amethyst commit 175b79b29 (April 2024)
Garnet fork date ~July 10, 2024
Latest amethyst commit a4310317a (May 2026)
Commits since fork ~9,330
Garnet version v0.3.0
Monero source https://github.com/retrnull/monero (branch: release-v0.18.3.3-garnet)
Current status Phase 1-5 compiled. MoneroScreen UI ported, Account reactive flows added. UI ready, needs navigation wiring.

What was accomplished (this session)

Previous session accomplishments (Phase 1-4)

  • Extracted all monero model classes from Garnet Kotlin code
  • Adapted imports for new KMP quartz package structure (nip01Core.*)
  • Created wallet model classes (Wallet, PendingTransaction, Subaddress, etc.)
  • Created WalletService (fully implemented from Garnet source, ~700 lines)
  • Created MoneroWalletListener interface
  • Created MoneroTipEvent in quartz (moneroTip package)
  • Created TipSplitSetup utility in quartz (moneroTip package)
  • Created TransactionPriority enum (UNIMPORTANT, SLOW, MEDIUM, FAST, ESTIMATED)
  • Created TipHandler and TipEventDataSource (stubs)
  • Created MoneroDataSource (stub with flows)
  • Added monero wallet fields to Account.kt (spend key, seed, restore height, password flows)
  • Added monero wallet methods to Account.kt (startMonero, stopMonero, balance, address, generateSpendKey)
  • Added moneroAddress() method to User model in commons
  • Added moneroAddressFromAbout() method to UserMetadataCache in commons
  • Added moneroAddressIsValid() utility function in commons
  • Added EventFactory registration for MoneroTipEvent (kind 1814)
  • Added monero.xml icon to resources
  • Added monero-specific strings to strings.xml
  • Added monero JNI bridge files (monerujo.cpp, monerujo.h, wallet2_api.h)
  • Updated amethyst/build.gradle.kts for monero dependencies

This session accomplishments (Phase 5)

  • Wired up MoneroDataSource.setMoneroService() - forwards WalletService StateFlows into MoneroServiceFlows
  • Added missing Account monero methods - sendMonero, listMoneroAddresses, newSubaddress, setSubaddressLabel, seedWithPassphrase, getMoneroHistory, getMoneroRestoreHeight, getMoneroDaemonStatus
  • Added Account monero settings properties - moneroDaemonAddress, moneroDaemonUsername, moneroDaemonPassword, moneroRestoreHeight, moneroPassword, moneroSeed, isMoneroSeedBackedUp, defaultMoneroTransactionPriority (+ setters)
  • Added MoneroUtils.kt - showMoneroAmount and decToPiconero helper functions
  • Restored TipEventDataSource.getEvent() - simplified from Garnet Flow-based pattern to LocalCache.getOrCreateNote
  • Kotlin compilation passes with 0 errors - verified with compileFdroidDebugKotlin

UI Port accomplishments (MoneroScreen)

  • Created MoneroScreen.kt (~795 lines) adapted to Amethyst patterns:
    • Uses Amethyst's MaterialSymbols from commons.icons.symbols (no compose-material-icons-extended needed)
    • Uses context.getString() and stringRes() for string resources (not Garnet's Flow-based pattern)
    • All composables implemented: MoneroScreen, MoneroSendDialog, ReceiveDialog, EditDaemonDialog, DaemonInfo, Balance, TipInfo, ActionsRow, BackupSeedDialog, EphemeralWalletWarning, TransactionList, TransactionRow
    • All buttons use M3ActionDialog from Amethyst components
    • All icons use MaterialSymbols from commons (ArrowDownward, Info, Settings, CloudUpload, Warning, ContentCopy, ExpandLess, ExpandMore, Send, etc.)
    • Uses HostAndPort via Account setter methods (changeMoneroDaemonAddress, changeMoneroDaemonUsername, changeMoneroDaemonPassword)
  • Added 12 new string resources for MoneroScreen UI (unconfirmed, monero_tipping_info, no_transactions, transaction_history, custom_restore_height, restoration_height, monero_amount, edit_address_label, info, close, no_results_found, transaction_received, transaction_sent)
  • Compiled with 0 errors - verified with compileFdroidDebugKotlin
  • Warning: Divider is deprecated → use HorizontalDivider

Account Reactive State Flows (Session)

  • Added 8 reactive StateFlows to Account.kt: moneroStatus, moneroBalance, moneroLockedBalance, moneroWalletHeight, moneroDaemonHeight, moneroConnectionStatus, moneroTransactions, moneroAddress
  • Added updateMoneroState() method to sync wallet service state into reactive flows

What's remaining (next session)

  • Wire MoneroScreen into app navigation (Routes.kt + AppNavigation.kt)
  • Run ./gradlew assembleFdroidDebug end-to-end after MoneroScreen addition
  • Add NIP-69 monero address support
  • Test monero wallet functionality end-to-end

Next Session Quick Start

To resume work on the Monero integration:

  1. Check current state:

    ./gradlew assembleFdroidDebug | tail -3
    
  2. Verify MoneroScreen.kt exists and compiles:

    find ~/projects/amethyst -name "MoneroScreen.kt" -not -path "*/build/*"
    ./gradlew compileFdroidDebugKotlin 2>&1 | grep -E "^e:|FAILED|SUCCESS"
    
  3. The next step: add MoneroScreen navigation route.

Relevant files:

  • amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/MoneroScreen.kt (main UI file)
  • amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/routes/Routes.kt (add route)
  • amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/AppNavigation.kt (wire screen)
  • amethyst/src/main/java/com/vitorpamplona/amethyst/model/Account.kt (reactive flows)

Known Issues & Gotchas

  • TransactionDirection enum is in com.vitorpamplona.amethyst.model package
  • TextSpinner is in com.vitorpamplona.amethyst.ui.components.TextSpinner
  • Deprecated Divider - use HorizontalDivider instead in newer Compose
  • MaterialSymbols - all icons must come from commons.icons.symbols.MaterialSymbols (not androidx.compose.material.icons). MaterialSymbols.AutoMirrored.Send for the Send icon.

What Worked ✓

21 New Files Created

# File Description
1 amethyst/src/main/java/.../model/MoneroWalletListener.kt JNI wallet listener callback interface
2 amethyst/src/main/java/.../model/PendingTransaction.kt JNI pending transaction wrapper
3 amethyst/src/main/java/.../model/ProofInfo.kt Proof verification data class
4 amethyst/src/main/java/.../model/Subaddress.kt Monero subaddress model
5 amethyst/src/main/java/.../model/Transaction.kt Monero transaction model
6 amethyst/src/main/java/.../model/TransactionInfo.kt Transaction info wrapper
7 amethyst/src/main/java/.../model/TransactionHistory.kt Transaction history model
8 amethyst/src/main/java/.../model/Transfer.kt Transfer data class
9 amethyst/src/main/java/.../model/Wallet.kt JNI Wallet wrapper (all native methods)
10 amethyst/src/main/java/.../model/WalletManager.kt Wallet factory + native bridge utilities
11 amethyst/src/main/java/.../service/MoneroDataSource.kt Data source stub with Flow wrappers
12 amethyst/src/main/java/.../service/TipEventDataSource.kt Tip event lifecycle stubs
13 amethyst/src/main/java/.../service/TipHandler.kt Tip processing logic (partial)
14 amethyst/src/main/java/.../service/WalletService.kt Android Service + all wallet operations
15 amethyst/src/main/java/.../ui/screen/loggedIn/TransactionPriority.kt Transaction priority enum
16 amethyst/src/main/res/drawable/monero.xml Monero icon resource
17 quartz/src/commonMain/kotlin/.../moneroTip/MoneroTipEvent.kt Nostr tip event (kind 1814)
18 quartz/src/commonMain/kotlin/.../moneroTip/TipSplitSetup.kt Tip split utilities + monero tag parsing
19 amethyst/src/main/cpp/monerujo.cpp JNI bridge C++ implementation
20 amethyst/src/main/cpp/monerujo.h JNI bridge header
21 amethyst/src/main/cpp/wallet2_api.h Monero wallet API header

6 Modified Files

# File Changes
1 amethyst/src/main/java/.../model/Account.kt Added monero wallet fields + methods
2 amethyst/src/main/res/values/strings.xml Added monero string resources
3 commons/src/commonMain/kotlin/.../commons/model/User.kt Added moneroAddress(), moneroAddressIsValid()
4 commons/src/commonMain/kotlin/.../commons/model/nip01Core/UserMetadataCache.kt Added moneroAddressFromAbout()
5 quartz/src/commonMain/kotlin/.../utils/EventFactory.kt Registered MoneroTipEvent (kind 1814), import
6 amethyst/build.gradle.kts Added monero module/dependencies

Current File Structure with Exact Import Paths

This is the most critical section. All Garnet-era import paths were different because the quartz module was not yet Kotlin Multiplatform. Here are the exact current paths every imported type should use:

Quartz (MoneroEvent) Import Paths

CRITICAL: These are DIFFERENT from Garnet source.

Type New import path
Event base class com.vitorpamplona.quartz.nip01Core.core.Event
HexKey com.vitorpamplona.quartz.nip01Core.core.HexKey
IEvent com.vitorpamplona.quartz.nip01Core.core.IEvent
AddressableEvent com.vitorpamplona.quartz.nip01Core.core.AddressableEvent
JsonMapper com.vitorpamplona.quartz.nip01Core.core.JsonMapper
OptimizedSerializable com.vitorpamplona.quartz.nip01Core.core.OptimizedSerializable
TagArray com.vitorpamplona.quartz.nip01Core.core.TagArray
KeyPair com.vitorpamplona.quartz.nip01Core.crypto.KeyPair (was com.vitorpamplona.quartz.nip01Core.enc.KeyPair in garnet)
NostrSignerInternal com.vitorpamplona.quartz.nip01Core.signers.NostrSignerInternal (was ...signs.NostrSignerInternal)
NostrSigner com.vitorpamplona.quartz.nip01Core.signers.NostrSigner
EventTemplate com.vitorpamplona.quartz.nip01Core.signers.EventTemplate
ETag com.vitorpamplona.quartz.nip01Core.tags.events.ETag
PTag com.vitorpamplona.quartz.nip01Core.tags.people.PTag
EventHintProvider com.vitorpamplona.quartz.nip01Core.hints.EventHintProvider
PubKeyHintProvider com.vitorpamplona.quartz.nip01Core.hints.PubKeyHintProvider
NormalizedRelayUrl com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl

Package Locations

Type Location
Note class com.vitorpamplona.amethyst.commons.model.Note (was amethyst-only before, now in commons)
User class com.vitorpamplona.amethyst.commons.model.User (was amethyst-only before, now in commons)
LocalCache com.vitorpamplona.amethyst.model.LocalCache (stays in amethyst)
MoneroTipEvent com.vitorpamplona.quartz.moneroTip.MoneroTipEvent
TipSplitSetup com.vitorpamplona.quartz.moneroTip.TipSplitSetup

Old Garnet → New Amethyst Mapping

Many Garnet-era packages have been renamed. Common mappings:

Old (Garnet/2024) New (2026)
com.vitorpamplona.quartz.events.EncryptedContentUtils com.vitorpamplona.quartz.nip01Core.crypto.EventHasher
com.vitorpamplona.quartz.events.* com.vitorpamplona.quartz.nip01Core.* (various subpackages)
com.vitorpamplona.quartz.events.signs.* com.vitorpamplona.quartz.nip01Core.signers.*
com.vitorpamplona.quartz.events.enc.* com.vitorpamplona.quartz.nip01Core.crypto.*
com.vitorpamplona.quartz.events.crypto.* com.vitorpamplona.quartz.nip01Core.crypto.*
commons.model.* (amethyst) commons.model.* (commons-commonMain)

NostrSigner API Change (CRITICAL)

The signing API changed significantly. Garnet-era code uses callback-based signing. Current Amethyst uses coroutine-based signing.

Old (callback, Garnet-era)

// Garnet's NostrSigner callback style
signer.sign(eventBuilder, onReady: (Event) -> Unit)

New (coroutine, current)

// Current Amethyst coroutine style
val event = signer.sign(template)
// or
val event = signer.sign(createdAt, kind, tags, content)

NostrSignerInternal signing with createdAt

In current code, NostrSignerInternal.sign() with createdAt uses the vararg tags variant:

import com.vitorpamplona.quartz.nip01Core.signers.NostrSignerInternal
import com.vitorpamplona.quartz.nip01Core.crypto.KeyPair

val event: MoneroTipEvent = NostrSignerInternal(KeyPair())
    .sign(createdAt = TimeUtils.now(), kind = MoneroTipEvent.KIND, tags = tags.toTypedArray(), content = content)

See LnZapRequestEvent.kt:130 for a real example of creating an anonymous signer for private zaps.


LocalCache.consume() Signature

The justConsume() method in LocalCache takes 3 parameters:

fun justConsume(event: Event, relay: NormalizedRelayUrl?, isOwn: Boolean): Boolean
Parameter Description
event The Nostr event to consume
relay The relay it came from (null if from client)
isOwn Whether this is the current account's own event

Common usage patterns:

// Consume from relay, not own event
cache.justConsume(event, relay, false)

// Consume own event (no relay)
cache.justConsumeMyOwnEvent(event)  // convenience wrapper, same as justConsume(event, null, true)

// Consume gift wrap / inner event from push
cache.justConsume(innerGift, null, false)

// Consume decrypted inner event from Marmot
cache.justConsume(innerEvent, null, true)

User Metadata Access Pattern

User metadata in commons.model.User is accessed via lazy-initialized caches:

// UserMetadataCache
val cache = user.metadata()           // lazy init
val flow: StateFlow<UserInfo?> = cache.flow
val name = cache.bestName()           // String?
val picture = cache.profilePicture()  // String?
val lnAddress = cache.lnAddress()     // String?

// For monero:
val moneroAddr = cache.moneroAddressFromAbout()  // String?

In User model, the moneroAddress() method tries nip69Address() first (not yet implemented), then falls back to moneroAddressFromAbout():

fun User.moneroAddress(): String? {
    val nip69 = nip69Address()       // NIP-69 type-0 event, stub returns null
    if (nip69 != null) return nip69
    return metadataOrNull()?.moneroAddressFromAbout()  // regex from about field
}

The regex in moneroAddressFromAbout(): \b[48]\w{93}\b — matches standard Monero addresses (4xxxx or 8xxxx, 95 chars total).


Amethyst.instance Reference Pattern

The global Amethyst singleton uses these accessors:

import com.vitorpamplona.amethyst.Amethyst

val context = Amethyst.instance.appContext          // NOT applicationContext
val customScope = Amethyst.instance.customScope     // UI coroutine scope
val ioScope = Amethyst.instance.applicationIOScope  // IO coroutine scope

Important: Use appContext, NOT applicationContext. Garnet-era code used applicationContext which no longer compiles.

See WalletService.kt:68-80 for the correct pattern:

Amethyst.instance.appContext.getString(R.string.wallet_status_opening)
Amethyst.instance.applicationIOScope  // used for scope.launch in wallet listener

TransactionPriority Enum

Defined at: amethyst/src/main/java/.../ui/screen/loggedIn/TransactionPriority.kt

enum class TransactionPriority(val value: Int) {
    UNIMPORTANT(0),
    SLOW(1),
    MEDIUM(2),
    FAST(3),
    ESTIMATED(4),
}

Used everywhere Monero transactions are created:

wallet.createTransaction(
    destination = address,
    amount = amount,
    priority = TransactionPriority.UNIMPORTANT  // passes priority.ordinal to JNI
)

WalletService Public API

WalletService (Android Service) implements the full wallet. All public methods:

Method Return Description
loadWallet(name, password, spendKey="", daemonAddress, ...) Wallet Open or create wallet
load(name, password, spendKey="", daemonAddress, ...) Wallet Same, with checkNotInMainThread
connectToDaemon(address, username, password, proxy, restoreHeight, walletPassword) Wallet.Status? Connect to daemon
connect(address, username, password, proxy, isCreation, restoreHeight, walletPassword) Wallet.Status? Same, check not on main thread
sendTransaction(destination, amount, priority) PendingTransaction.Status? Send to single address
sendTransactionMultDest(destinations[], amounts[], priority) PendingTransaction? Multi-destination
getTxProof(txId, destination, message) Proof? Generate transaction proof
checkTxProof(txId, address, message, signature) ProofInfo? Verify a transaction proof
setProxy(proxy) Boolean? Set network proxy
storeWallet() Wallet.Status? Persist wallet to disk
closeWallet() Unit Close the current wallet
newSubaddress(accountIndex, label) Subaddress? Generate new subaddress
lastSubaddress(accountIndex) Subaddress? Get last subaddress
listAddresses() List<Subaddress>? List all subaddresses
setSubaddressLabel(index, label) Wallet.Status? Label a subaddress
seedWithPassphrase(passphrase) String? Get wallet seed
isAddressValid(address) Boolean? Validate address format
getRestoreHeight() Long? Get restore height
getHistory() TransactionHistory? Get transaction history
setUserNote(txId, note) Boolean? Attach user note to transaction
estimateTransactionFee(destinations[], amounts[], priority) Long? Estimate fee in picoMonero
getBalance Long Property: total balance
getLockedBalance Long Property: locked (unconfirmed) balance
walletHeight Long Property: wallet blockchain height
daemonHeight Long Property: daemon blockchain height
address String? Property: primary address
connectionStatus Wallet.ConnectionStatus Property: DISCONNECTED/CONNECTED/WRONG_VERSION

State flows (MutableStateFlow → StateFlow):

  • statusStateFlow: StateFlow<WalletStatus>
  • balanceStateFlow: StateFlow<Long>
  • lockedBalanceStateFlow: StateFlow<Long>
  • walletHeightStateFlow: StateFlow<Long>
  • daemonHeightStateFlow: StateFlow<Long>
  • connectionStatusStateFlow: StateFlow<Wallet.ConnectionStatus>
  • transactions: StateFlow<List<TransactionInfo>>

MoneroDataSource — Current State

File: amethyst/src/main/java/.../service/MoneroDataSource.kt

object MoneroDataSource {
    @Volatile
    private var flows: MoneroServiceFlows = MoneroServiceFlows()

    fun status(): Flow<*> = flows.status

    fun connectionStatus(): Flow<*> = flows.connectionStatus

    fun walletHeight(): Flow<Long> = flows.walletHeight

    fun daemonHeight(): Flow<Long> = flows.daemonHeight

    fun balance(): Flow<Long> = flows.balance

    fun lockedBalance(): Flow<Long> = flows.lockedBalance

    fun transactions(): Flow<List<TransactionInfo>> = flows.transactions

    fun setMoneroService(service: WalletService) {
        val newFlows =
            MoneroServiceFlows(
                status = service.statusStateFlow,
                connectionStatus = service.connectionStatusStateFlow,
                walletHeight = service.walletHeightStateFlow,
                daemonHeight = service.daemonHeightStateFlow,
                balance = service.balanceStateFlow,
                lockedBalance = service.lockedBalanceStateFlow,
                transactions = service.transactions,
            )
        flows = newFlows
    }
}

The setMoneroService() forwards all 7 WalletService StateFlows into MoneroServiceFlows. It must be called from AccountViewModel or service binding to wire the flows.


What Next Session Must Do

Priority A: Wire MoneroScreen into Navigation

  1. Add Monero route in Routes.kt (sealed class Route)
  2. Add composable mapping in AppNavigation.kt
  3. Wire from settings screen or top navigation drawer

Priority B: Add NIP-69 Monero Address Support

NIP-69 defines a peer-to-peer way to share Monero addresses via Nostr events. Consider implementing a type-0 NIP-69 event for monero address sharing.

Priority C: End-to-End Testing

  • Test wallet creation and daemon connection
  • Test sending and receiving Monero
  • Test subaddress generation
  • Test tip events with monero tags

Build Commands

Verify baseline builds

cd ~/projects/amethyst && ./gradlew assembleFdroidDebug | tail -3

Get the diff between Garnet and the fork point

cd ~/projects/garnet
git diff 99614f07..bfaf92e5 --stat    # 103 files
git diff 99614f07..bfaf92e5 --name-status  # file list with A/M/D status

Extract native libs from garnet releases

curl -L -o /tmp/garnet.apk "https://github.com/retrnull/garnet/releases/download/v0.3.0/app-mainnet-fdroid-universal-release.apk"
unzip -o /tmp/garnet.apk -d /tmp/garnet/
# libmonerujo.so is in extracted-libs/lib/<abi>/
cd ~/projects/amethyst && ./gradlew compileFdroidDebugKotlin 2>&1 | grep -E "^e:|FAILED|SUCCESS"

Count monero compilation errors

./gradlew compileFdroidDebugKotlin 2>&1 | grep "^e:" | grep -i monero | wc -l

Watch monero-specific errors in real time

./gradlew compileFdroidDebugKotlin 2>&1 | tee /tmp/build.log | grep "^e:" | grep -i monero

Common Error Patterns We Fixed

These are specific compile/lint errors encountered during the port, with fixes applied:

1. No such file or directory: 'com.vitorpamplona.quartz.events.*'

Cause: Quartz module restructured from main/java/com/vitorpamplona/quartz/events/ to commonMain/kotlin/com/vitorpamplona/quartz/nip01Core/

Fix: Replace all old imports with new package paths. Mapping table above.

2. Cannot access 'com.vitorpamplona.quartz.nip01Core.enc.KeyPair'

Cause: KeyPair moved from enc.* to crypto.*

Fix: com.vitorpamplona.quartz.nip01Core.crypto.KeyPair

3. Unresolved reference: NostrSignerInternal from signs.*

Cause: Signers package renamed from signs to signers

Fix: com.vitorpamplona.quartz.nip01Core.signers.NostrSignerInternal

4. Val cannot be reassigned / Property setter not found

Cause: Garnet used var properties; current codebase uses val with delegation

Fix: Use cache.update(...) pattern or MutableStateFlow.update { } instead of direct assignment.

5. Cannot inline class: KeyPair

Cause: KeyPair was a regular class in Garnet; now it may be a value class or internal class

Fix: Use KeyPair constructor properly or access through NostrSignerInternal factory.

6. Unresolved reference: applicationContext on Amethyst.instance

Cause: Amethyst renamed applicationContext to appContext

Fix: Amethyst.instance.appContext

7. Function 'justConsume' doesn't have the expected number of parameters

Cause: Garnet used 1-parameter justConsume(event), new version uses 3: justConsume(event, relay, isOwn)

Fix: Add relay and isOwn parameters: cache.justConsume(event, null, true) for own events.

8. Type mismatch: inferred type is commons.model.User but ...

Cause: User moved from amethyst.model.User to commons.model.User

Fix: Update all imports: com.vitorpamplona.amethyst.commons.model.User

9. No parameter with name 'autoCorrect' in TextField

Cause: Material3 TextField removed autoCorrect parameter in the new Compose BOM

Fix: Remove autoCorrect = false parameter from TextField calls.

10. Unresolved reference: rememberRipple

Cause: Material3 ripple API changed/removed

Fix: Use rememberRipple(color = ...) or remove the ripple parameter from composables.

11. Cannot find heading or HeadingStyle

Cause: HeadingStyle enum moved or was renamed during Compose upgrade

Fix: Check current richtext package for the correct heading/composable type.

12. No parameterless constructor found on sealed interfaces

Cause: Some sealed hierarchies moved from sealed class to sealed interface

Fix: Create instances via companion object create() methods or object expressions, not constructors.


Quick Commands for New Session

All paths use ~/ or $HOME — do NOT use absolute /home/ paths:

# Verify amethyst builds
cd ~/projects/amethyst && ./gradlew assembleFdroidDebug | tail -3

# Count remaining monero errors
./gradlew compileFdroidDebugKotlin 2>&1 | grep "^e:" -i monero | wc -l

# Get garnet diff for reference
cd ~/projects/garnet && git diff 99614f07..bfaf92e5 --stat

# Check current monero files in amethyst
find ~/projects/amethyst -name "*.kt" -path "*monero*" -not -path "*/build/*"

# Find all new files added this session
git status --short | grep "^A "

# Find all modified files
git status --short | grep "^M "