mirror of
https://github.com/vitorpamplona/amethyst.git
synced 2026-10-05 19:28:25 +00:00
fix(relay-auth): say whose conversation the relay is holding back
The thread prompt read "It won't serve the rest of this conversation unless you log in" without saying which conversation — and these prompts routinely surface over the home feed or the settings screen, so "this" pointed at something the reader did not have on screen at all. The information was there and being discarded. Thread reads are `#e` against note ids, which the assemblers either declare as `entityIds` or (ReactionsFilterAssembler) only put in the filter's `e` tags; the deriver kept neither, emitting a bare AuthPurpose(THREAD). It now carries those ids in a new `notes` field — separate from `venues`, because a thread is not a room — and the dialog resolves them to their authors through the same counterparty label the other purposes use, so the sentence gets a name and an inline avatar. "this conversation" survives only as the fallback for a thread whose notes are not in the cache yet. Verified on device: "It won't serve the rest of your conversation with Alice Peer unless you log in." Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
88f81bbff9
commit
0e13a3b7a7
+24
-2
@@ -149,7 +149,26 @@ private fun RelayAuthPromptDialog(
|
||||
|
||||
// The purpose the user is most likely to recognize as "what I was just doing".
|
||||
val primary = remember(prompt) { prompt.purposes.primary() }
|
||||
val faces = primary?.counterparties?.toList().orEmpty()
|
||||
|
||||
// A thread names nobody by itself, so the sentence used to say "the rest of this conversation"
|
||||
// about something the reader may not have on screen — these prompts routinely surface over the
|
||||
// home feed or the settings screen. Resolving the notes to their authors turns the vague
|
||||
// reference into a person the reader recognizes.
|
||||
val threadFaces =
|
||||
remember(primary) {
|
||||
if (primary?.kind == AuthPurposeKind.THREAD) {
|
||||
primary.notes.mapNotNullTo(LinkedHashSet()) { LocalCache.getNoteIfExists(it)?.author?.pubkeyHex }.toList()
|
||||
} else {
|
||||
emptyList()
|
||||
}
|
||||
}
|
||||
|
||||
val faces =
|
||||
primary
|
||||
?.counterparties
|
||||
?.toList()
|
||||
.orEmpty()
|
||||
.ifEmpty { threadFaces }
|
||||
val who =
|
||||
when (primary?.kind) {
|
||||
AuthPurposeKind.POST_VENUE, AuthPurposeKind.READ_VENUE ->
|
||||
@@ -380,7 +399,10 @@ private fun reasonFor(
|
||||
AuthPurposeKind.POST_VENUE -> stringRes(R.string.relay_auth_why_post_venue, who ?: "")
|
||||
AuthPurposeKind.READ_VENUE -> stringRes(R.string.relay_auth_why_read_venue, who ?: "")
|
||||
AuthPurposeKind.MY_INBOX -> stringRes(R.string.relay_auth_why_my_inbox)
|
||||
AuthPurposeKind.THREAD -> stringRes(R.string.relay_auth_why_thread)
|
||||
// Name the conversation by who is in it when we could resolve the notes; "this conversation"
|
||||
// only survives as the fallback for a thread whose notes aren't in the cache yet.
|
||||
AuthPurposeKind.THREAD ->
|
||||
if (who.isNullOrBlank()) stringRes(R.string.relay_auth_why_thread) else stringRes(R.string.relay_auth_why_thread_with, who)
|
||||
// No attributable purpose. If it is the user's own relay we can at least say that much,
|
||||
// which is the only way MY_OWN_RELAY is ever reachable — the deriver is account-agnostic
|
||||
// (one shared socket, many accounts) so it cannot know whose relay this is.
|
||||
|
||||
+10
-2
@@ -89,6 +89,7 @@ object RelayAuthPurposeDeriver {
|
||||
|
||||
val readAuthors = mutableSetOf<HexKey>()
|
||||
val readVenues = mutableSetOf<String>()
|
||||
val readThreadNotes = mutableSetOf<String>()
|
||||
var readsMyInbox = false
|
||||
var readsThread = false
|
||||
var unattributedRead = false
|
||||
@@ -99,7 +100,14 @@ object RelayAuthPurposeDeriver {
|
||||
// Declared and self-contained: nobody else's identity is involved, so there is
|
||||
// nothing to collect — the flag alone drives the wording.
|
||||
AuthPurposeKind.MY_INBOX -> readsMyInbox = true
|
||||
AuthPurposeKind.THREAD -> readsThread = true
|
||||
// The notes are what makes the sentence nameable. The assembler either declares
|
||||
// them (entityIds) or, as ReactionsFilterAssembler does, only puts them in the
|
||||
// `e` tags — take whichever we get so the prompt can say whose conversation.
|
||||
AuthPurposeKind.THREAD -> {
|
||||
readsThread = true
|
||||
explained?.entityIds?.let(readThreadNotes::addAll)
|
||||
filter.tags?.get("e")?.let(readThreadNotes::addAll)
|
||||
}
|
||||
// Declared, but the *who*/*what* still comes from the filter. Prefer the entity
|
||||
// ids the assembler named over sniffing tags, and fall back when it named none.
|
||||
AuthPurposeKind.READ_VENUE -> {
|
||||
@@ -135,7 +143,7 @@ object RelayAuthPurposeDeriver {
|
||||
if (readAuthors.isNotEmpty()) add(AuthPurpose(AuthPurposeKind.READ_OUTBOX, readAuthors))
|
||||
if (readVenues.isNotEmpty()) add(AuthPurpose(AuthPurposeKind.READ_VENUE, venues = readVenues))
|
||||
if (readsMyInbox) add(AuthPurpose(AuthPurposeKind.MY_INBOX))
|
||||
if (readsThread) add(AuthPurpose(AuthPurposeKind.THREAD))
|
||||
if (readsThread) add(AuthPurpose(AuthPurposeKind.THREAD, notes = readThreadNotes))
|
||||
// Safety net: we're using this relay but couldn't say how — prompt rather than fail silently.
|
||||
if (isEmpty() && (unattributedWrite || unattributedRead)) add(AuthPurpose(AuthPurposeKind.OTHER))
|
||||
}
|
||||
|
||||
@@ -1161,6 +1161,7 @@
|
||||
<string name="relay_auth_why_read_venue">It won\'t serve %1$s unless you log in.</string>
|
||||
<string name="relay_auth_why_my_inbox">It won\'t serve your incoming replies, zaps and messages unless you log in.</string>
|
||||
<string name="relay_auth_why_thread">It won\'t serve the rest of this conversation unless you log in.</string>
|
||||
<string name="relay_auth_why_thread_with">It won\'t serve the rest of your conversation with %1$s unless you log in.</string>
|
||||
<string name="relay_auth_why_my_own_relay">This is one of your own relays, and it asks everyone to log in.</string>
|
||||
<string name="relay_auth_why_other">It asks everyone to log in. Amethyst can\'t tell what it\'s holding back.</string>
|
||||
<string name="relay_auth_someone_unloaded">someone you haven\'t loaded yet</string>
|
||||
|
||||
+7
@@ -75,6 +75,13 @@ data class AuthPurpose(
|
||||
val kind: AuthPurposeKind,
|
||||
val counterparties: Set<String> = emptySet(),
|
||||
val venues: Set<String> = emptySet(),
|
||||
/**
|
||||
* Event ids of the conversation being read, for [AuthPurposeKind.THREAD]. Kept separate from
|
||||
* [venues] because a thread is not a room: these are note ids, and the only use for them is to
|
||||
* resolve *whose* conversation it is at render time, so the prompt can say "your conversation
|
||||
* with Alice" instead of "this conversation" about something the user may not have on screen.
|
||||
*/
|
||||
val notes: Set<String> = emptySet(),
|
||||
)
|
||||
|
||||
/** The relay plus every live reason we currently have to auth with it. */
|
||||
|
||||
Reference in New Issue
Block a user