Kgothatso Ngako a14ea3d9d3 feat(dkg): every key ceremony a room holds, pending first
A room holds more than one ceremony as a matter of course, and has done since a
subgroup's ChillDKG moved into its parent's Marmot room: the parent's own, long
finished, plus one for every subgroup the group has ever made. Nothing in the app
could name the second of them.

`DkgRitualRoute` without a session id shows the room's *newest*, which is routinely the
finished one with the subgroup's request buried underneath it. The group's details
reached only that. The transcript could name one, by scrolling to the line it was
about -- which for a subgroup means scrolling past whatever else the room has been used
for, and is impossible for a ceremony whose line never arrived.

**`DkgRitualListScreen`, deliberately the same shape as `ProposalListScreen`.** One row
per ceremony off `observeSessionsForChatRoom`, split into "waiting for you" and
"everything else". Split rather than sorted, for the reason `ProposalListUIState`
gives: the rest of the list is what the room has done, these are what it is waiting on
the reader for, and they are a different kind of thing to read. The finished ones are
kept, because how this group came to hold the keys it holds is the other half of what
the screen is for.

**What "waiting on you" means is two things, and they are owed by different people.**
An approval gate this member cannot delegate, and a finished ceremony whose room only
its coordinator can create -- a ceremony reaching COMPLETE finishes nothing on its own,
and nothing else in the app would tell the coordinator so. Both are asked of
`DkgRepository` rather than re-derived, the same readings the transcript's standing
notice and the ceremony screen make, so the three cannot disagree. An abandoned
ceremony is asked neither: there is no step left to approve and no key to build a room
from, and a row claiming otherwise would send a member to a screen with nothing on it.

**A row says what the key is for before it says where it has got to**, because a room
holding two holds them for different groups and a row that only reported progress would
leave a member guessing which. A subgroup is named by its proposal, so there is a third
branch for the member who arrives before that name has -- it still says which of the
two kinds of key this is. The quorum is on the row too: a ceremony fixes it and it
cannot be changed afterwards.

**Three entry points, two of which were gaps.**

`ChatRoomDetailScreen` gets a "Key ceremonies" row in *every* room, including the
Marmot ones the `mlsGroupState` check keeps "Shared key" out of. The two are different
questions and both are kept: "Shared key" offers a room a key of its own, which only a
NIP-17 room can want; this lists the ceremonies a room *holds*. A parent's Marmot room
has never had a door to its subgroups' ceremonies at all.

The transcript's `CeremoniesAwaitingYouNotice` says "3 key ceremonies need you" and
then opened `ceremonies.first()` -- answering a different question from the one it
asked, and leaving the other two exactly as hard to find as before. It now opens the
list whenever it stands for more than one, which is what the signing notice beside it
already did.

A ceremony line in the transcript routes to the list when it cannot name a session -- a
line written before chat rows carried one -- or when the ceremony it names is not the
only one waiting on this member. `hidesOtherCeremonies` mirrors `hidesOtherDecisions`
exactly, including its restraint: only for a ceremony that is itself waiting on them,
because a line about a finished one is history and a reader who taps it is asking to
see that, not to be handed a queue.

**An empty list offers the way out of itself**, rather than ending in a blank page that
sends a member back to the group's details to find the other door. It only offers to
ask; the ceremony screen still decides whether the group is in a position to hold one.

`DkgRitualListViewModel` is a `MutableStateFlow` from the start rather than a
`by mutableStateOf`, for the reason the commit before this one found.

**Tests, 6 cases in 2 files.** `DkgRitualListViewModelJvmTest` covers the three
decisions nothing else can check: that a ceremony wanting something is at the top even
when its date is not, that only the coordinator is told to finish setting up a finished
one, and that an abandoned ceremony is waiting on nobody even when both lookups say it
is. `DkgRitualListScreenJvmTest` renders it and checks the thing that is only true once
drawn -- that a row opens the ceremony it names rather than the room's newest, which is
the whole reason the screen exists -- plus the two headings and the empty state's offer.

**Not done.** Nobody has looked at this screen running; the render tests prove it
composes, sections and routes, which is not the same thing.

970 jvm tests, 454 common tests, `m3Audit` meeting every budget.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-10-02 19:46:06 +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%