`ChatRoom` was two things in one row keyed by the group id: the group's own facts -- its id, name and description -- and the asking profile's membership of it, which is its MLS ratchet state, when it joined and left, whether it asked for a chronicle, and whether it has been archived. A second profile joining the same group wrote over the first's ratchet state, which breaks both members in the shape of "the other device never got it". `679d5249` refuses that Welcome; this removes the reason it had to. **Split rather than a wider key.** Keying the room by (`id`, `userPublicKey`) was the obvious reading and the worse schema. Of the nineteen tables referencing a room, fourteen hold the *group's* signed work -- `GroupSignedEvent`, `GroupKeyState` and the twelve `Mantra*` tables -- and two profiles in one group read the same chapters. Those fourteen would have had to carry a `userPublicKey` that misrepresents them or drop their foreign key and the cascade with it; `GroupSignedEvent` could not have carried one at all, its key being the event's hash. Splitting leaves all nineteen pointing at a room that is unambiguously the group's. `ChatRoomDao` follows from that: a listing joins the membership, because "mine" and "archived" are things one profile says about a group, while a lookup by id stays the group's and filters nobody's `deletedAt` -- a manager asking for the room it is about to act in wants the group, whether or not somebody tidied it away. **The migration turns on one fact, and the fact is now measured.** Rebuilding `ChatRoom` is unavoidable (a foreign key names two of the columns to remove, so `DROP COLUMN` will not), and with foreign keys enforced no rebuild survives: the drop fires all nineteen cascades, and `RENAME TO` rewrites the children's clauses to follow it -- `PRAGMA legacy_alter_table` does **not** prevent that, which I checked rather than assumed. A migration cannot disable foreign keys itself either, inside Room's transaction. So this depends on Room disabling them, exactly the kind of dependency `MIGRATION_9_10` warns against taking on trust -- and `ChatRoomSplitThroughRoomJvmTest` asserts it against a real v22 file through `getRoomDatabase`, with rows seeded in four of the nineteen. An earlier draft of this migration lost every child row; that test is what caught it. The replacement is also built *beside* the original and renamed into place after it is gone, so nothing is ever renamed that the children point at. The acting profile now comes from where it is actually known. `DatabaseChatRepository` and `DatabaseDkgRepository` read it off the `activeIdentity` they already hold, the inbound MLS path derives it from the `activeKeyPair` it is already given, and `ChatMessageListViewModel` takes it from the screen that opened it. Leaving a group and archiving one are a membership's acts now, so one profile doing either leaves the other's list alone. 1008 tests, 0 failures. Schema export committed beside its predecessors. 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…