02e70d9claimed the paths past the membership guard "need a real peer key package to exercise, which means an MLS fixture this test file deliberately does not build", and left them uncovered on that basis. That was wrong, and this corrects it. Nothing about a key package needs a relay. DatabaseMarmotRepository.generateKeyPackage already builds this device's own entirely locally: two X25519 key generations, one Ed25519, a leaf node signed under "LeafNodeTBS" and a key package signed under "KeyPackageTBS". Everything it touches is quartz public API, so MarmotKeyPackageFixture replicates it in about forty lines. The capabilities it advertises are not decoration -- the group's RequiredCapabilities rejects a leaf that does not carry LastResort and NostrGroupData, so a fixture omitting them is refused at addMember rather than at decode, and the comment says so. With that, three properties past the guard are asserted rather than described. The invitee is persisted. sealGiftWrapPayload walks the room's participants to decide who to wrap a Welcome for, so without the row the Welcome produces no gift wraps at all and sits unsealed forever. The advanced epoch reaches the database. addMember moves the in-memory group forward, and the comment on that write explains what happens when it is not saved back: the creator keeps encrypting under the old epoch, which the new member cannot decrypt, and the next invite re-derives from stale state and produces a conflicting commit. The test asserts the stored state changed, that it still restores, and that the restored group has two members -- so it is checking a real advance rather than any write at all. The epoch being left behind is retained, at epoch 0 for a freshly created group. That is the call that actually writes a retained secret, so it belongs here as well as in the retention-window tests that only read them. Verified by mutation: deleting the write that persists the advanced state fails `inviting a member persists the advanced group state` and nothing else. The mutation was reverted; no production source is touched by this commit. The peer needs a Profile row here where the guard tests did not, because Participant.participantPublicKey is a foreign key onto Profile and only a successful invite reaches that write -- the same constraint that shapes the nip17 tests in5fa0d08. Found the same way, by three of these failing with SQLite 787 first. Still not covered: the Welcome itself, the deferred-welcome path for a group that already has members, and the batching in addMembersToChatRoom. Those need more than a key package -- a second device's view of the group -- and are a separate piece of work. 3 tests added, 7 in the class. composeApp jvmTest is 312 tests, 0 failures. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This is a Kotlin Multiplatform project targeting Android, iOS, Desktop (JVM).
-
/composeApp is for code that will be shared across your Compose Multiplatform applications. It contains several subfolders:
- commonMain is for code that’s common for all targets.
- Other folders are for Kotlin code that will be compiled for only the platform indicated in the folder name. For example, if you want to use Apple’s CoreCrypto for the iOS part of your Kotlin app, the iosMain folder would be the right place for such calls. Similarly, if you want to edit the Desktop (JVM) specific part, the jvmMain folder is the appropriate location.
-
/iosApp contains iOS applications. Even if you’re sharing your UI with Compose Multiplatform, you need this entry point for your iOS app. This is also where you should add SwiftUI code for your project.
Build and Run Android Application
To build and run the development version of the Android app, use the run configuration from the run widget in your IDE’s toolbar or build it directly from the terminal:
- on macOS/Linux
./gradlew :composeApp:assembleDebug - on Windows
.\gradlew.bat :composeApp:assembleDebug
Build and Run Desktop (JVM) Application
To build and run the development version of the desktop app, use the run configuration from the run widget in your IDE’s toolbar or run it directly from the terminal:
- on macOS/Linux
./gradlew :composeApp:run - on Windows
.\gradlew.bat :composeApp:run
Build and Run iOS Application
To build and run the development version of the iOS app, use the run configuration from the run widget in your IDE’s toolbar or open the /iosApp directory in Xcode and run it from there.
Learn more about Kotlin Multiplatform…