Phase 5 of docs/subgroups-multiple-parents.md, the UI. Phases 1-4 were dark; this is the flag, and it is the button. A group can now be given a second parent from the app. **Two affordances, on the child's own detail screen, under the parent cards.** Both belong there and not on the adopting group's screen: the child is what acquires the relationship, its quorum is what decides, and it mirrors the parent screen's existing "add subgroup". `SelectAdoptingGroupScreen` is the only new screen, because the acceptance needs none -- by then there is something to accept and a card does it. Its list is the **profile's own rooms** and cannot be anything else: a certificate is proposed inside the adopting group, so a candidate has to be a room the key in the chair can act in. `adoptingCandidatesFor` filters through `refuseAdoption` itself rather than through a copy of its rules, so screen and manager cannot drift. An empty list is a fact about this member rather than a failure, and the empty state says the reason outright -- a group cannot be offered as a parent by somebody who is not in it, so the remedy is a person rather than a button. **Tapping a row asks; it does not adopt, and the words have to carry that.** The snackbar says "asked that group to adopt this one", and the screen hands straight back to the room rather than going anywhere new. The offer card then says a group is a subgroup of another *only once its own quorum says so*, because rendered as a fact about the lineage it would be the consent gap reopened from the other end -- the one Phase 3 closed in the transcript. **The accept button is offered to whoever opens the screen and can act**, not only to the member who asked, for the reason docs/subgroups.md gives for the ritual screen's rung: every step after a ceremony is open to any admin holding a share. After it, the screen reloads rather than crossing the offer off -- the offer leaves the list when the state is signed and filed, which is a quorum away, so the card stays until the group has actually agreed. `canAddParent` wants a key state as well as a share, which `canAddSubgroup` does not: the certificate carries this group's threshold key and the acceptance re-announces that very state, so a room that has agreed nothing has neither half to work with. Hidden rather than disabled, the way the subgroup entry is. The picker is a pushed screen, so it takes an `onNavigateBack` and puts `NavigateBackButton` in its app bar's leading slot -- the rule `CLAUDE.md` gained while this was still a note, and the desktop target has neither a system back nor an edge swipe to fall back on. `offersTo` reads every certificate this device holds in **any** room rather than asking this one, because a certificate about this group is signed in the other group's room and filed against it -- asking this room would never find it. Each is checked by `classify`, and parents already in the lineage drop out, since an offer that has been accepted is not an offer. Ten strings in the catalogue in sentence case, spacing from `MaterialTheme.spacing`, colour from roles, `Decorative` on every icon beside a label, and the notifier read outside the handler with the caller's scope -- an acceptance is shown while the screen reloads around it. `inComparableGroups` stays private to `ChatRoomDetailScreen`: the upstream commit that moved it to the key extensions is in the fork's tree and not this one, and a second copy to add three spaces to an id is not worth having. **What is reachable and what is not.** Adding a parent and accepting one are both wired. `SubgroupManager.proposeDisavowal` is built, tested and has no button -- dropping a parent is a destructive-looking act that wants a confirmation step, and it belongs in its own change rather than bolted onto this one. 454 common tests, 962 jvm tests, `m3Audit` meets every budget with 0 title-case strings and 0 dp literals. 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…