Kgothatso Ngako e15d31b832 feat(subgroups): ask a group to adopt this one, and accept the offer when it comes
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>
2026-10-02 18:16:58 +02:00
2026-09-09 17:04:09 +02:00
2026-03-24 04:47:19 +02:00
2026-03-23 01:41:39 +02:00
2026-03-23 01:41:39 +02:00
2026-03-23 01:41:39 +02:00

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…

Description
Mantra as a kotlin multiplatform project
https://mantra.press
Readme 13 MiB
Languages
Kotlin 100%