Kgothatso Ngako 39b1c714b8 feat(groups): a broadcast button under each signed event, and the relays it goes to
Until now nothing a group signed reached a relay. `FrostSigningManager.complete`
files the batch as `GroupSignedEvent` rows and applies it locally, and the profile,
the relay lists, the posts and the schemas are all read off those rows -- the docs
on each said so, and the only way out was the copy button on the key-state sheet.
This is the way out: a "Broadcast" button under every signed event in the identity
block, opening `BroadcastGroupSignedEventScreen`, where the member sees and edits
the relays it will go to, presses once, and reads what each relay answered. The raw
JSON is at the bottom for anyone who would rather use another tool.

**The send goes to the relay pool directly, not through the broadcast queue.**
Everything the app publishes as itself goes through `BroadcastNostrEventRequest`,
which is durable and retried, and it is not used here on purpose. The queue hangs
off a `NostrEvent` row, and a row for the group's event is more than a queue entry:
the home feed selects by kind, so the group's post would appear in it as anybody's;
and the sync loop offers every local event id to the relays it reconciles with, so
the event would reach relays the screen never named -- which would make "edit the
relays" a fiction. `GroupSignedEvent` already explains at length why it is not a
`NostrEvent`; this keeps that true. `EventPublishTransport` is the slice of
`RelaysSocketManager` the screen needs, cut the way `LiveSubscriptionTransport` is
and for the same reason: the manager cannot be stood up in a test. The cost is that
a broadcast is only as durable as the button press. Each row says what its relay
said, and a relay that did not answer is sent to again by pressing again.

**Behind no gate.** Every other button in the block is hidden from a member holding
no share of the key, because each proposes a signature by the group. Sending what
the group has already signed takes no share: the signature is the group's whoever
repeats it, and any member could paste the JSON into another client today. So the
button is there for whoever is looking, the way the suggestion queue is.

**Where it goes before anyone says otherwise** is `defaultRelaysFor`: the group's
General list where it has agreed one -- its write relays, and nothing from the app's
set beside them, since a member who edited the relays meant them -- and the app's
publish set where it has not. A relay list also goes to the indexers, because a list
sent only to the relays it names is circular; a curated schema also goes to the
relays it names for its own replies, so a client reading suggestions there finds
what they answer. Nothing goes to a relay the group has blocked. The seed happens
once, so a list the member emptied stays empty.

**Each relay's answer is shown in the relay's words.** The pool answers once per
relay in three shapes -- an OK, a rejection wrapped in `NostrPublishException`, and
any other error -- and the rows keep them apart, because "blocked: not on the allow
list" is something a member can act on and a red icon is not. A relay still
unanswered when the whole send runs out of time reads "No answer", which is not the
same as refused. Answers are matched by host, since the pool names a relay by the
URL it first opened a socket with.

The relay-list rows get their button inside the shared card, under the row and at
the end, because the four rows share one card and "under the event" has to mean
under the row. An agreed empty list gets one too: a withdrawal is a statement, and
relays still holding the old list need to hear it. The key-state event and the
subgroup certificates get none -- `ChronicleManager` keeps the key state off even
the members-only path, and a public relay is a bigger audience than that.

`BroadcastGroupSignedEventViewModelJvmTest` pins the seed rules, the once-only
seeding, the three answer shapes and the host matching, and that what goes on the
wire is the seven fields the group hashed. `BroadcastGroupSignedEventScreenJvmTest`
presses the button against a fake pool and finds each answer under its relay with
the sum above them. `GroupNostrProfileSectionJvmTest` counts five buttons on a group
with a profile, two agreed lists, a post and a schema, each under its own card, and
finds them still there for a member with no share.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Pulled-From: curated/curated@6ae5a9676e
2026-09-13 12:01:41 +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%