Add a third and final group-creation step, after the member picker, that
asks whether the new group should be convenient (the creator is its only
admin) or robust (every member is an admin and a change needs a
threshold of them to approve it).
The flow is now:
ChatRoomCreationRoute (name + description)
-> SelectChatRoomMembersRoute (pick people)
-> SelectChatRoomTypeRoute (how it is run, then build it)
-> ChatRoomMessagingRoute
New: database/model/types/ChatRoomType.kt
* CONVENIENT / ROBUST, plus the two rules that go with them.
* approvalThreshold(adminCount) is a simple majority, so no half of the
group can move without the other.
* MINIMUM_ROBUST_GROUP_SIZE = 3 and isRobustAvailable(memberCount).
Below three a majority is not a meaningful check: at two admins every
change needs both of them, and at one the creator is deciding alone,
which is CONVENIENT under another name.
New: ui/composable/navigation/routes/SelectChatRoomTypeRoute.kt
* Carries activeUserPublicKey, name, description and the picked
memberPublicKeys. The governance choice feeds the epoch-0 group
context, so nothing can be persisted until it has been made and every
earlier answer has to ride along to this step.
* memberPublicKeys is a List<String>. Navigation 2.9.2 resolves that
through NavType.StringListType (NavTypeConverter maps
InternalType.STRING inside a collection onto it), so it needs no
hand-rolled encoding.
New: ui/view/state/SelectChatRoomTypeUIState.kt
* Loading/Loaded/Error, with Loaded carrying the picked members so the
screen can name them rather than echo hex keys.
New: ui/view/model/SelectChatRoomTypeViewModel.kt
* Owns the choice (selectedChatRoomType, convenient by default because
it is the option that always works) and the group creation and invite
round, both moved here wholesale from SelectChatRoomMembersViewModel.
* The choice is not cosmetic: it decides adminPubkeys on the epoch-0
MarmotGroupData. CONVENIENT stamps just the creator; ROBUST stamps the
creator plus every picked member, deduplicated (MIP-01 rejects
duplicates). MarmotInboundManager.processGroupMembershipChanges
already derives Participant.adminAt from exactly this list, so admin
status propagates to every member's device without further work.
* MarmotGroupData.bootstrap() still stamps the base metadata -- the
admin list is layered on with copy() -- so UI and CLI stay
byte-identical on everything else.
* selectChatRoomType() refuses ROBUST when the group is too small, and
freezes once the room exists: by then the choice is baked into the
epoch-0 group context and re-picking would change nothing.
* robustUnavailableReason spells out the shortfall ("Go back and add 1
more person"), pluralised here rather than in the composable.
* Known gap, left as a TODO next to the admin list: robust rooms get the
admin set but not the t-of-n approval itself. That needs FROST signing
over admin changes -- the same thing the existing "generate GID
through frost" TODO is waiting on. Until then every admin of a robust
room can still commit on their own.
New: ui/composable/SelectChatRoomTypeScreen.kt
* Two radio cards. Convenient explains that the creator acts alone and
that nobody can carry the group on without them; robust quotes the
real numbers -- "approved by 2 of 3 admins" -- computed from the
actual selection instead of leaving t-of-n abstract.
* Below MINIMUM_ROBUST_GROUP_SIZE the robust card renders disabled
(Card(enabled = false), disabled RadioButton, no click) and shows the
reason in the error colour. It stays on screen rather than vanishing,
so the option is discoverable and the fix -- go back, tick one more
person -- is obvious.
* Carries the bottom bar the members step used to own: the
create/progress/"Open chat" action and the partial-invite warning,
which now name members via displayNameFor() since this step only
receives keys.
SelectChatRoomMembersViewModel / SelectChatRoomMembersScreen
* Reduced to what their names say. Group creation, the invite round, the
key package lookup, the wallet flow and ChatRepository all move to the
type step; what stays is listing local profiles, ticking them, and
handing the keys on through SelectChatRoomTypeRoute.
* The up-front key package sync stays here, which is the point of doing
it early: it now has the whole type-selection step to land in before
anybody is invited.
* The action becomes "Next with N" and the empty-store copy no longer
offers to create the chat, because this step no longer can.
MantraNavHost
* Register SelectChatRoomTypeRoute. Opening the finished chat still pops
back through ChatRoomCreationRoute inclusive, so backing out of a new
chat lands where the user started rather than part-way through the
three creation steps.
Verified:
./gradlew :composeApp:compileCommonMainKotlinMetadata
./gradlew :composeApp:compileDebugKotlinAndroid
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…