Commit Graph
17647 Commits
Author SHA1 Message Date
Claude a7748dfda5 fix: clear compiler warnings across all modules
Sweep of Kotlin compiler warnings in every module's main source sets
(quartz, commons, cli, desktopApp, amethyst, nappletHost).

Genuine code fixes:
- Drop unnecessary !!/safe-calls and redundant elvis/casts (OkHttp's
  now-non-null `body`, smart-cast callbacks, non-null String receivers).
- Remove provably-redundant conditions (`canvas == null` after a
  non-null content check; `account != null` implied by `canModerate`).
- Migrate deprecated kotlinx.collections.immutable persistent ops
  (add/remove/put/addAll -> adding/removing/putting/addingAll).
- Migrate LocalClipboardManager -> LocalClipboard (+ scoped setText),
  ContextCompat.startActivity -> context.startActivity, TabRow ->
  SecondaryTabRow, and @ConsistentCopyVisibility on a private-ctor data class.
- Delete dead ReceiveDialog.onGenerate param (never invoked).
- Fix a platform-Boolean type-mismatch on a ThreadLocal read.

Deprecations with no available successor are narrowly @Suppress-ed with
a reason: androidx.security.crypto (EncryptedSharedPreferences/MasterKey),
androidx.privacysandbox.ui, WebView.databaseEnabled, BluetoothDevice
.connectGatt, media3 setEnableAudioTrackPlaybackParams, FirebaseMessaging
.token, and InputMethodManager.SHOW_IMPLICIT.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015Xb9YbBqhdZsHxMzitmyvn
2026-07-24 16:36:17 +00:00
Vitor PamplonaandGitHub c6fd7095d4 Merge pull request #3690 from davotoula/fix/localcache-duplicate-streammessage-branch
Remove unreachable duplicate StreamMessageV2Event branch in computeReplyTo
2026-07-24 11:37:50 -04:00
Vitor PamplonaandGitHub c5369da6db Merge pull request #3691 from vitorpamplona/claude/calendar-rsvp-buttons-layout-408xgu
Improve RSVP button layout and text handling
2026-07-24 10:09:25 -04:00
David KasparandGitHub 8a61e8c5ff Merge pull request #3689 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2026-07-24 16:02:35 +02:00
vitorpamplonaandgithub-actions[bot] d15c0d1eab chore: sync Crowdin translations and seed translator npub placeholders 2026-07-24 12:54:13 +00:00
Vitor PamplonaandGitHub 7c1300b735 Merge pull request #3687 from nrobi144/feat/desktop-moderation-safety
feat(desktop): Moderation & Safety — mute/block enforcement, NIP-56 report, NIP-36 content warning
2026-07-24 08:51:16 -04:00
davotoulaandClaude Opus 4.8 34ceaafa78 fix(buzz): remove unreachable duplicate StreamMessageV2Event branch in computeReplyTo
LocalCache.computeReplyTo had two `is StreamMessageV2Event ->` branches in the
same `when`. The `when` matches the first, so the second (the older
buzzThreadRoot+buzzThreadReply variant) was dead code — the condition Sonar
flags as duplicating the earlier one. The surviving first branch is the newer,
deliberate Concord-style threading behavior that links a 40002 thread reply into
its parent's replies via buzzThreadReply. Also drops the now-unused
buzzThreadRoot import.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EoYFvsXvPigRWebuoGiRoG
2026-07-24 14:38:02 +02:00
davotoula f02490a078 Merge remote-tracking branch 'upstream/main' into main-upstream 2026-07-24 14:24:49 +02:00
David KasparandGitHub 6fb78c33b6 Merge pull request #3688 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2026-07-24 14:10:22 +02:00
davotoula 5aab994649 update cs,sv,de,pt 2026-07-24 13:07:57 +02:00
davotoulaandgithub-actions[bot] 611f030207 chore: sync Crowdin translations and seed translator npub placeholders 2026-07-24 10:52:56 +00:00
David KasparandGitHub bf8c2413a9 Merge pull request #3681 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2026-07-24 12:50:08 +02:00
nrobi144andClaude Opus 4.8 1a0b9eb2d9 docs(desktop): moderation test results (PASS) with published-report logs
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-24 12:17:28 +03:00
nrobi144andClaude Opus 4.8 55d11acd61 feat(desktop): unify note ⋮ + right-click menus and add snackbar feedback
- rememberNoteMenuActions is now the single source for the note menu; both the
  ⋮ overflow (ShareMenu) and the feed right-click ContextMenuArea render the same
  items (copy ×5 / broadcast / mute / report).
- LocalSnackbarHost exposes the app SnackbarHostState so moderation actions show a
  confirmation: 'Muted user', 'Report sent', 'Reported & muted', 'Broadcast to
  relays', and failure toasts. Wired for the note menu + profile ⋮ actions.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-24 12:04:23 +03:00
nrobi144andClaude Opus 4.8 c555265f0b docs(desktop): moderation UX + reporting manual testing sheet
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-24 11:57:03 +03:00
nrobi144andClaude Opus 4.8 65579ffc18 feat(desktop): log moderation publishes (report/mute) + surface zero-relay sends
Report/mute actions published silently with no feedback. Add a DesktopModeration
logger: every report/mute logs kind+id+relay-count on publish, WARNs when there
are 0 connected relays (so a dropped publish is visible instead of silent), and
signing/publish failures are caught + logged (were swallowed by the launching scope).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-24 11:55:36 +03:00
nrobi144andClaude Opus 4.8 df442bf7b5 fix(desktop): discoverable note moderation — right-click menu + ⋮ overflow
Manual testing showed the note moderation actions were hard to find:
- Provide LocalDesktopIAccount on MainContent's own (non-null) provider so every
  deck/feed/profile/settings surface reliably resolves the account.
- Add a right-click ContextMenuArea on feed notes: Mute user / Report… / Copy text
  (moderation items for other authors on a writeable account).
- Swap the note action-row Share icon for a MoreVert (⋮) overflow — the menu it
  opens already carries copy/broadcast + mute/report, matching the profile ⋮.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-24 11:51:51 +03:00
vitorpamplonaandgithub-actions[bot] 7cc4c63996 chore: sync Crowdin translations and seed translator npub placeholders 2026-07-24 04:32:24 +00:00
Vitor PamplonaandGitHub 313e04b7df Merge pull request #3683 from vitorpamplona/claude/push-notification-design-fzyutz
Redesign push notifications with per-kind styles and channels
2026-07-24 00:29:22 -04:00
Claude 02c59b332e fix: keep calendar RSVP buttons on a single line
The Going / Maybe / Can't go buttons in the calendar feed card wrapped
to two lines because the default button content padding (24dp horizontal)
left no room for "Can't go" in an equal-weight third of the row.

Tighten the content padding, shrink the row spacing, and constrain each
label to a single line so the three buttons fit horizontally.
2026-07-24 04:04:07 +00:00
Claude d91c15d97b fix: attribute reply-notification parent to its real author, not always "Me"
The parent message shown above a reply in the MessagingStyle notification was
unconditionally labeled "Me" with the account's avatar. That's wrong whenever
the account is notified as the *root* author of a thread but the direct parent
belongs to someone else — e.g. Vitor starts a thread, fiatjaf replies, a third
person replies to fiatjaf: fiatjaf's note was rendered as Vitor. This surfaced
through the NIP-22 comment and public-chat reply paths, which notify on
root-authorship, not just direct-parent authorship.

ReplyNotification now receives the parent Note (not a bare content string) and
resolves its actual author: attributed to the MessagingStyle `me` Person only
when the parent truly is the account's, otherwise shown as the real author with
their name + avatar, observed for enrichment like the replier. postConversation
reuses the `me` Person for a self-authored parent so it still renders as you.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0122uQ8BLHLeHDni81RBP26r
2026-07-24 03:45:49 +00:00
Claude 6df7398d02 feat: render inline image links as the notification big picture
When a mention/article body contains an inline `http(s)` image link, show the
image as the notification's big picture (BigPictureStyle) instead of leaving the
raw URL in the text. NotificationContent.renderNoteText() now pulls the first
image URL out of the content — scanning all lines, since images usually sit on
their own line — using the cheap RichTextParser.isImageUrl extension check, and
strips that link from the excerpt. Videos are left in the text (Coil can't load
them as a still). The Mention and Article renderers pass the extracted URL as
bigPictureUrl; Reply stays MessagingStyle (a big picture doesn't fit a chat
bubble) and keeps its text as-is.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0122uQ8BLHLeHDni81RBP26r
2026-07-24 02:59:19 +00:00
Claude c5b8062a0c feat: resolve inline @npub mentions to display names in notifications
Notification bodies previously showed raw `nostr:npub1…` / `nostr:nprofile1…`
tokens for anyone cited in the text — the name never filled in even after the
cited user's kind:0 loaded, because the excerpt was a static substring taken
once and never re-resolved against the metadata cache.

Add NotificationContent.resolveMentions(), which rewrites each cited npub/
nprofile token to `@<best display name>` and returns the cited Users so the
renderer can add them to its enrichment window. Event references (nevent/note/
naddr) are left verbatim — they have no name to show. Mention, Reply, Media and
Article renderers now recompute the body inside the observable build closure and
observe the cited users, so the text flips from `@npub1abc…` to `@RealName` in
place, matching the author-name enrichment the title already had.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0122uQ8BLHLeHDni81RBP26r
2026-07-24 02:53:39 +00:00
Claude c5941942be Merge remote-tracking branch 'origin/main' into claude/push-notification-design-fzyutz 2026-07-23 21:36:31 +00:00
Vitor PamplonaandGitHub 5800571b77 Merge pull request #3686 from vitorpamplona/fix/concord-list-and-channel-previews
fix(concord): recover communities that won't load + warm channel previews
2026-07-23 17:31:29 -04:00
Vitor PamplonaandClaude Opus 4.8 69ef23d7ed feat(concord): warm channel previews so the list fills without opening each channel
Concord fetched a channel's messages only when its screen was open (the history
pager mounts on the channel screen), so an un-opened channel showed "No messages
yet" in the community list and never appeared in the Messages inbox — unlike
NIP-28 / NIP-29, which preload a last-message per room. Add a one-shot warm
drain, `Account.warmConcordChannelPreviews`, triggered on community-screen open
and app-wide per subscribed community from the account preload (both debounced
so the cold-boot fold burst warms once).

Per channel (`ConcordSubscriptionPlanner.channelPreviewFilters`):
- never read -> the newest `previewLimit` (10) wraps: a preview plus a rough
  sense of how busy the channel is, without pulling the whole backlog.
- read -> everything `since lastRead - 1` (capped at `catchUpLimit`): the unread
  badge is accurate and the missed messages are cached for on-open; the `-1`
  re-includes the last-read message (its created_at == lastRead) so a caught-up
  channel still shows a preview, and unread stays exact (the count is strict `>`).

Filters group by relay into one REQ per relay (one filter per channel); the
wraps ingest through the normal cache path, and the always-on plane subscription
keeps them fresh afterward. Full history still pages in on open.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-23 17:26:09 -04:00
Vitor PamplonaandClaude Opus 4.8 31acb5037b fix(concord): decode the community list even when an entry has a keyless channel
A public channel carries no delivered key, so a writer lists it under an
entry's `channels` with only {id, epoch, name}. `WireChannel.key` was a
required field, so kotlinx.serialization threw MissingFieldException — and
`decodeDocument`'s catch-all turned that one bad channel into an empty list,
silently dropping EVERY joined community from the kind-13302 list (communities
"won't load" at all).

- Default `WireChannel.key = ""` so a keyless (public) channel no longer throws.
- Decode entries one at a time and keep any we still can't parse verbatim in
  `ConcordListResidue.unparsedEntries`, re-emitted on write — so one malformed
  entry can never wipe the whole list, and a read-modify-write never deletes a
  membership this version can't model.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-23 17:25:52 -04:00
Claude 5001d4a27a test(notifications): cover Buzz reactions in the e2e harness + document Buzz DM
- run.sh: add a Buzz-style bare reaction check (kind-7 with an `e` tag but no `p`
  tag) asserting it still lands on the Reactions channel, and a note that Buzz DM
  isn't auto-triggered.
- README: coverage row for the bare reaction, plus a "Buzz DM (manual)" section
  with the channel-setup steps (kind-39000 `t=dm` + participant, then post
  kind-9/40002) since participant-routing needs cached channel metadata.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0122uQ8BLHLeHDni81RBP26r
2026-07-23 21:25:29 +00:00
Claude 77dd1a5585 feat(notifications): Buzz DM push notifications + repost mute parity
Buzz DM messages (NIP-29 relay-group StreamMessageV2Event kind 40002 / ChatEvent
kind 9 in a `t=dm` channel) carry no `p` tag — participation is the relevance
signal — so main added them to the in-app feed only. This wires them into push:

- BuzzDmNotification renderer: MessagingStyle on the Private Messages channel,
  channel name as the conversation title, "sender: text" body, sender avatar,
  deep-links to the relay-group chatroom via the channel's kind-39000 naddr
  (reusing the existing naddr → Route.RelayGroup path). Honors the
  "show messages in notifications" toggle and enriches the sender observably.
- Dispatcher: adds kinds 40002/9 to NOTIFICATION_KINDS and gates them
  EXCLUSIVELY on Buzz-DM membership (buzzDmChannelForMe) so ordinary kind-9
  chats (Concord / NIP-C7) don't leak into the tray.
- NotificationRoutes.relayGroupUri for the channel deep-link.

Also closes a mute-parity gap the audit surfaced: the push muted-thread check
covered Reaction/LnZap but not Repost/GenericRepost (the feed mutes all four) —
so a bare Buzz repost of your note on a muted thread now stays muted in push too.

Buzz reactions/reposts (bare no-`p`-tag likes) already route to the existing
Reaction/Repost renderers, which are correct for Buzz (plain kind-7/6/16, same
target resolution and emoji semantics) — verified, no change needed.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0122uQ8BLHLeHDni81RBP26r
2026-07-23 21:02:54 +00:00
Vitor PamplonaandGitHub acebf96eb9 Merge pull request #3684 from vitorpamplona/claude/pow-miner-cancel-behavior-18ckmk
Add "send without PoW" option for template posts during mining
2026-07-23 16:46:45 -04:00
Claude c55f46cb2b feat(notifications): push-notify Buzz-style bare reactions/reposts to your note
A Buzz like is a bare ["e", <id>] reaction with no p tag, so the push gate's
(isTaggedUser || publicChatReply) pre-clause rejected it even though
tagsAnEventByUser already recognizes a reaction/repost whose reacted note is
mine. Relax the pre-clause for reaction/repost kinds; tagsAnEventByUser keeps it
scoped to reactions on my own note. Reuses the existing Reaction/Repost
renderers.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0122uQ8BLHLeHDni81RBP26r
2026-07-23 20:33:49 +00:00
Claude 2db2f8d3ca Merge remote-tracking branch 'origin/main' into claude/push-notification-design-fzyutz 2026-07-23 20:00:26 +00:00
Claude 7a3fc48f07 feat: offer "send without PoW" when abandoning a mining job
Cancelling a proof-of-work mining job used to silently discard the post
(the sign+broadcast continuation only ran on a successfully mined
template). Users had no way to publish the note un-mined once they'd
started waiting.

Now abandoning a template post asks what to do instead of discarding:

- The × on the mining banner opens a dialog with "Send without PoW",
  "Discard post", or tap-away to keep mining. Only shown for jobs that
  carry a plain un-mined fallback (template posts); opaque work jobs
  (reactions, reposts, anonymous posts, gift wraps) keep the direct
  cancel since they have no template to fall back to.
- The mining foreground-service notification gains a "Send now" action
  that publishes every eligible queued post without proof of work.

Implementation: the queue keeps the un-mined publish continuation
alongside the miner. sendWithoutPow() sets a flag the worker picks up on
its next isActive poll; the miner aborts and the plain template is
published through the same sign+broadcast path the mined template would
have used, off the worker pool. PoWJobState exposes canSendWithoutPow so
the UI knows which jobs support it.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012kLVFV7ps4HfXDDJPNqi82
2026-07-23 20:00:16 +00:00
Vitor PamplonaandGitHub a6e6903f6d Merge pull request #3682 from vitorpamplona/claude/buzz-repo-analysis-7k54ga
Add Buzz protocol support with workspace, DM, and agent features
2026-07-23 15:45:09 -04:00
Claude a657a9698d fix(buzz): use assertTrue instead of assert in ForumCommentEventTest
Kotlin/Native marks the stdlib `assert()` with `@ExperimentalNativeApi`, so
the iOS test target (`compileTestKotlinIosSimulatorArm64`) failed to compile
without an opt-in — while JVM/Android were fine. Swap it for `assertTrue`
from `kotlin.test`, which needs no opt-in on any target and matches the
assertions already used in this file.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01J8KBSw6smQRyXLiWHeDsZ8
2026-07-23 19:19:27 +00:00
Claude 210aa86c2f Merge remote-tracking branch 'origin/claude/buzz-repo-analysis-7k54ga' into claude/buzz-repo-analysis-7k54ga 2026-07-23 18:43:47 +00:00
Claude 9ab1c650a9 feat(buzz): move workspace add-people & invite-link into top-bar overflow menu
The two workspace-owner actions (Add people to this workspace, Create invite
link) sat as an inline button row above the channel list. Move them into a
3-dot overflow (MoreVert) menu in the top-right of the workspace top bar,
matching the Concord community pattern. Fold the invite-mint flow (spinner,
result Copy/Share dialog, error dialog) into the new BuzzWorkspaceOverflowMenu
and drop the now-obsolete BuzzInviteMintButton.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01J8KBSw6smQRyXLiWHeDsZ8
2026-07-23 18:43:24 +00:00
Vitor PamplonaandGitHub 66108d8aa1 Merge pull request #3680 from davotoula/feat/644-warn-dms-from-reported-senders
Warn users when a DM comes from an account that people they follow have reported
2026-07-23 14:41:30 -04:00
Vitor PamplonaandClaude Opus 4.8 3a9d9a7b5d fix(buzz): show bare Buzz reactions (e-tag only) in Notifications
A Buzz like is a bare kind-7 `["e", <msg-id>]` — no `p` tag (unlike NIP-25,
which p-tags the reacted author) and no `h` tag. The notifications filter's
p-tag gate and follow filter therefore both dropped it, so a like on my Buzz
message never surfaced even in Global mode, despite the reaction being loaded
(it rendered as a chip in the open conversation).

Recognize a reaction/repost that carries NO `p` tag whose reacted target — the
last `e` tag, already loaded — is my own note, and let it bypass the follow
filter and satisfy the p-tag gate (like a Concord reaction). Scoped to the
no-`p`-tag case so well-formed NIP-25 reactions keep their normal routing, and
to an already-loaded target so it can't accept blindly. The per-kind gate
already resolves the same target author, so it needs no change.

Verified on emulator: a 👍 + ❤️ on my Buzz DM message now appears on the
Curated notifications tab.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-23 14:35:41 -04:00
Vitor PamplonaandClaude Opus 4.8 8982af5752 feat(buzz): notify on reactions/zaps to my Buzz DM messages
The group-notification query (reactions/zaps/reposts on my messages inside a
NIP-29 group, kind-7 etc. scoped #p=me + #h on the host relay) sourced its #h
set from the published kind-10009 group list — which deliberately excludes Buzz
DM channels (membership is server-side, tracked in BuzzDmChannels). So a
reaction on my DM message was only ever loaded as a chip inside the open
conversation, never surfacing on the Notifications tab.

Include DM channels (from BuzzDmChannels, minus hidden ones) alongside the
joined groups in both the live-tail (AccountNotificationsEoseFromInboxRelays-
Manager) and backward-history (AccountNotificationsHistoryEoseManager) queries,
reusing the same filterGroupNotificationsToPubkey builder, and re-subscribe when
a DM is discovered/hidden. Regular-channel reactions already worked via the
joined-group path; this closes the DM gap the same way.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-23 14:35:41 -04:00
Claude 8600484d0b Merge remote-tracking branch 'origin/main' into claude/buzz-repo-analysis-7k54ga 2026-07-23 18:31:44 +00:00
davotoula 8b7dac5c0b Styling updates 2026-07-23 19:24:43 +02:00
davotoula 4405e20eb7 Code review:
- docs(dm): note the profile Reports tab also reads reportsNamingUser
- refactor(dm): push report-tag typing to quartz and simplify the warning stack
- fix(dm): narrow report indexing and address final review findings
- perf(dm): resolve a 1:1 chat row's counterpart once per row
2026-07-23 19:23:43 +02:00
davotoula 3e1d537eac feat(dm): flag reported counterparts on the chat list row
fix(dm): dedupe reporter avatars and align warning card styling
feat(dm): warn in the room when the counterpart is reported by a follow
feat(dm): expose a report-warning flow per user
refactor(reports): extract reusable reportTypeLabel composable
feat(reports): index author-named reports even when they target an event
feat(reports): add pure DM report-warning classifier
feat(reports): add additive reportsNamingUser index to UserReportCache
2026-07-23 19:23:43 +02:00
davotoula b2fa1d3b92 upgrade agp 2026-07-23 18:53:54 +02:00
David KasparandGitHub 5c4a7e8620 Merge pull request #3679 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2026-07-23 17:35:07 +01:00
Claude 647997f895 refactor(buzz): audit pass — fix draft-save invite bug, races, GC churn
Batch of correctness, performance, and code-quality fixes across the Buzz
workspace feature surfaced by a full audit of the branch's modified files:

- ChannelNewMessageViewModel: agent auto-invite ran inside createTemplate(),
  which sendDraftSync() calls per keystroke — so @mentioning a member
  published real kind-9000 invites while still typing. Move the invite to the
  send path (sendPostSync) and stage the pending mentions instead.
- AgentConsoleViewModel: fix a decryptCache read/reload race by guarding
  reloadFromCache (not refresh) under the mutex; bound observerSeen growth.
- BuzzNewDmViewModel: broaden start()'s catch so signer/IO/timeout failures
  surface as an error instead of leaving Start stuck on "Sending"; rethrow
  CancellationException.
- BuzzPresenceState / BuzzAgentActivityState: replace per-record full-map
  copies with persistent maps (structural sharing) to cut GC churn; the
  StateFlow now holds the map directly.
- RelayGroupChannelListScreen / RelayGroupMembersScreen / RelayGroupTopBar /
  RenderBuzzNotes: correct remember/LaunchedEffect keys so state rebinds when
  the relay or channel changes.
- RelayGroupDiscoveryFeedFilter: hoist joinedGroupIds() out of the per-item
  matches() loop.
- RelayGroupChannel / BuzzWorkspaceStates: @Volatile the fields read off relay
  dispatcher threads.
- BuzzInviteMinter: build the request URL through HttpUrl.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01J8KBSw6smQRyXLiWHeDsZ8
2026-07-23 16:32:26 +00:00
Vitor PamplonaandClaude Opus 4.8 995d0e8489 feat(buzz): render Buzz DM notifications as messages, not posts
Follow-up to the Buzz-DMs-in-Notifications work: a Buzz DM on the Notifications
tab rendered like a public post and its actions didn't fit a DM.

- Render as a conversation: new RenderRelayGroupMessage draws a
  RelayGroupChannelHeader (titled by the other participant for a DM) above the
  message body — the group/DM analog of RenderChatMessage/RenderChannelMessage.
  NoteCompose routes a group-scoped kind-9 (ChatEvent with a RelayGroupChannel
  gatherer) and kind-40002 (StreamMessageV2Event) to it. The in-conversation
  chat screen renders via RefreshingChatroomFeedView, not NoteCompose, so its
  bubbles are unaffected.

- Reply goes into the conversation, not a public kind:1111: routeReplyTo now
  detects the gathering RelayGroupChannel (mirroring the Marmot/Concord cases)
  and opens the group/DM instead of falling through to Route.GenericCommentPost.

- Action row matches the card: a relay-group message (incl. a Buzz DM) hides
  Repost and Share — both would reference a membership-gated group event that
  non-members can't fetch, and a DM shouldn't be rebroadcast. Reply/like/zap stay.

Verified on emulator: the DM card shows the participant header + body + a
reply/like/zap-only row, and tapping reply opens the DM conversation.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-23 12:03:56 -04:00
Vitor PamplonaandClaude Opus 4.8 eeeaab3c43 feat(buzz): surface Buzz DMs in the Notification feed
A Buzz DM is a relay-authoritative NIP-29 group whose messages carry no `p`
tag, so nothing made them eligible for the Notifications tab and nothing
fetched them app-wide (discovery was scoped to the open DM inbox, which only
pulls 44100 + 39000 — never the message bodies). Two halves fix that:

- NotificationFeedFilter now early-accepts a group chat message (kind-9 or
  kind-40002 — the deployed relay uses both) when it resolves to a `t=dm`
  channel whose 39000 participants include me, honoring the same "Messages in
  notifications" toggle and never notifying for my own message. LocalCache
  gains `getRelayGroupChannelForContent`, the read-only reverse-lookup this
  needs (same serving-relay-then-single-channel keying as the consume path).

- An always-on discovery (BuzzDmDiscoveryPreload) subscribes 44100 #p=me across
  joined workspaces into the new BuzzDmChannels registry and fetches each DM's
  39000 directory; BuzzDmJoinedChatTailFilterAssembler then keeps those
  channels' recent messages warm app-wide (reusing the joined-group #h tail),
  excluding hidden DMs. Both mount in LoggedInPage. This is what makes a Buzz
  DM show on Notifications / in push without opening the conversation.

Tests: BuzzDmChannels registry; and a LocalCache resolution test proving a
40002 and a kind-9 message both resolve back to their DM channel (and a
non-dm channel does not).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-23 12:03:56 -04:00
Claude 20384f5481 feat(buzz): add people to a channel/community + mint invite links
Three owner/admin add-people paths (the relay enforces the role gate):

- Channel add-member: an "Add member" FAB on the channel members screen
  opens a user-search dialog that publishes a NIP-29 kind-9000 put-user for
  the picked pubkey (plain member).
- Community add-member: an "Add people to this workspace" action in the Buzz
  community view publishes the Buzz relay-admin add-member command (kind
  9030) so the relay updates its NIP-43 membership list (13534). Wires the
  existing quartz RelayAdminAddMember/RemoveMember events into Account +
  AccountViewModel (addCommunityMember / removeCommunityMember).
- Invite-link mint: BuzzInviteMinter does the Buzz-specific, NIP-98-signed
  POST /api/invites on the relay host (via HTTPAuthorizationEvent + the
  trusted-relay OkHttp client) and returns {code, url, expires_at}; a
  "Create invite link" button surfaces the link with Copy / Share.

The user search is extracted into a reusable BuzzAddPeopleDialog shared by
the channel and community add flows. /api/invites is Buzz-proprietary (only
NIP-98 is standard); community add uses Buzz's 9030 admin command, since
NIP-43 itself has no owner-initiated add.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01J8KBSw6smQRyXLiWHeDsZ8
2026-07-23 15:48:50 +00:00