Commit Graph

11960 Commits

Author SHA1 Message Date
Vitor Pamplona e1a133ac9f removes unneeded annotation 2026-04-24 14:11:07 -04:00
Vitor Pamplona d8a8082887 removes the account retainer logic 2026-04-24 13:39:00 -04:00
Vitor Pamplona 711a70cdb4 Merge pull request #2539 from vitorpamplona/claude/fix-giftwrap-unwrap-2659O
Preload all writable accounts for always-on notifications
2026-04-24 13:28:29 -04:00
Vitor Pamplona 141cbf93c3 Merge pull request #2538 from vitorpamplona/claude/add-reply-notifications-GvMOn
Add reply and mention notifications for public notes
2026-04-24 13:27:14 -04:00
Vitor Pamplona 13fc014e66 Merge pull request #2548 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2026-04-24 13:14:55 -04:00
Claude 82e4448b58 refactor(quartz): move lowercase-p notification check into PTag
The default Event.notifies body was hand-rolling a tag scan for lowercase
`p`, duplicating the tag-shape knowledge that already lives in PTag.
Give PTag ownership of the check:

    PTag.isNotifying(tags, userHex)  // iterates tags and uses PTag.isTagged

Event.notifies now delegates to it. Kinds that override (CommentEvent,
WakeUpEvent) still work — they compose or replace the default as before.

https://claude.ai/code/session_01GQDJxiHPogdzCNhUBN7Pjc
2026-04-24 16:36:21 +00:00
Crowdin Bot b5671b7449 New Crowdin translations by GitHub Action 2026-04-24 16:19:07 +00:00
davotoula 999184cced optimise imports 2026-04-24 18:15:58 +02:00
Claude 10bbd0ddb2 refactor(notifications): per-kind Event.notifies(HexKey) routing
The notification pipeline previously hard-coded a lowercase-`p`-tag match
in two places (the observer predicate and consumeFromCache via
taggedUserIds). That's correct for most kinds but wrong for two:

- NIP-22 CommentEvent: a comment several levels deep only tags the root
  author via uppercase `P` (RootAuthorTag). Pure lowercase-p routing
  missed "someone replied deep in your thread" notifications.

- Experimental WakeUpEvent (kind 23903): its `p` tags are the authors of
  the subject events it references — Bob reacting to Alice's post yields
  a WakeUpEvent with p=Bob, even though Alice's device is the one that
  needs to wake up. Transport-layer routing (push/relay subscription)
  already delivered the event to the right device, so the in-event
  routing has to be permissive.

Introduce `open fun Event.notifies(userHex: HexKey): Boolean` with a
lowercase-`p` default that covers NIP-01/04/17/25/28/34/57/68/71/84/AC/
chess/wiki/long-form/poll mentions. Each kind with distinct semantics
overrides:

- CommentEvent.notifies: super.notifies(u) || rootAuthorKeys().contains(u)
  — picks up uppercase P root-author routing on top of lowercase p.
- WakeUpEvent.notifies: true — every logged-in account is a valid wake
  target once the event has reached LocalCache on this device.

NotificationDispatcher's observer predicate and EventNotificationConsumer.
consumeFromCache both now ask `event.notifies(accountHex)` instead of
extracting taggedUserIds themselves. The zap path's redundant
isTaggedUser re-check is gone too (it was duplicating what the outer
routing already enforced).

https://claude.ai/code/session_01GQDJxiHPogdzCNhUBN7Pjc
2026-04-24 16:06:55 +00:00
Vitor Pamplona 6a55c752e8 Merge pull request #2547 from greenart7c3/feat/reorder-payment-targets-icon
feat(reactions): place payment targets icon after zap by default
2026-04-24 11:47:21 -04:00
Claude 86a86e1426 refactor(notifications): observer takes a single predicate
Filter.match is itself just a boolean predicate, so carrying a Filter and
a separate composition predicate on NewEventMatchingFilter duplicated the
abstraction. Collapse to one predicate:

- NewEventMatchingFilter(predicate, onNew): drops the Filter parameter.
- LocalCache.observeNewEvents(predicate): primary API.
- LocalCache.observeNewEvents(filter): kept as a one-liner that forwards
  filter::match, so existing feed/DAL callers are unchanged.

In the dispatcher the freshness check, kind check, p-tag match, and
since cutoff now live in a single lambda, short-circuiting on cheap
kind comparison first. NOTIFICATION_KINDS is now a Set<Int> for O(1)
membership instead of O(n) List.contains — cheap, but on a hot path
that runs for every new cache insertion it pays for itself.

https://claude.ai/code/session_01GQDJxiHPogdzCNhUBN7Pjc
2026-04-24 14:39:42 +00:00
greenart7c3 e30413a081 feat(reactions): hide payment targets icon by default
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-24 11:15:00 -03:00
greenart7c3 4c8959dbd4 feat(reactions): place payment targets icon after zap by default
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-24 10:34:06 -03:00
Vitor Pamplona f2e7a622f1 Merge pull request #2546 from vitorpamplona/claude/increase-icon-size-x4vc7
Fix Material Symbol icon rendering and adjust bottom bar icon sizes
2026-04-24 09:16:48 -04:00
Claude 15e63c48a2 fix(icons): render Material Symbols with the correct tint
The painter measured the glyph with TextStyle(color = Unspecified) and
tried to override the color at draw time via drawText(layout, color = tint).
That override is unreliable across Compose versions: when the measured
style carries Color.Unspecified, drawText can fall through to Color.Black,
producing black-on-black icons (visible on the back arrow in dark mode).

Include the tint in the measured TextStyle directly. The (symbol, fontFamily,
density, tint, rtl) remember key on rememberMaterialSymbolPainter was already
keyed on tint, so the TextMeasurer cache still hits for every (symbol, color)
pair used by the app.
2026-04-24 13:04:30 +00:00
Claude 7ab23ce30e fix: increase bottom bar icon size to compensate for MaterialSymbols padding
MaterialSymbols glyphs have more internal padding than the previous
MaterialIcons, so at 20dp they looked smaller than before. Bump the
NavigationBar icons to 24dp (Material's default NavigationBar icon size)
and widen the notification-dot box accordingly to preserve its offset.
2026-04-24 12:55:28 +00:00
Vitor Pamplona 9874a44956 Merge pull request #2545 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2026-04-24 08:14:41 -04:00
Crowdin Bot 222a3255d1 New Crowdin translations by GitHub Action 2026-04-24 12:13:39 +00:00
Vitor Pamplona 6a001cd807 Merge pull request #2544 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2026-04-24 08:12:07 -04:00
Vitor Pamplona e0605058da Merge pull request #2543 from davotoula/fix/hand-raised-bug-and-sonar-fixes
Fix: hand raised toggle bug and sonar fixes
2026-04-24 08:11:57 -04:00
Crowdin Bot 5298684892 New Crowdin translations by GitHub Action 2026-04-24 12:08:04 +00:00
Vitor Pamplona 5c2d5dafb7 Merge pull request #2542 from davotoula/docs/security-policy
add SECURITY.md with private vulnerability reporting policy
2026-04-24 08:06:08 -04:00
davotoula 20367af3db fix(cli): warn when FileStores delete() fails
Delete() returning false on a still-existing file meant we silently lost
scratch state for MLS group / key-package / message stores. Route the
four call sites through a `deleteOrWarn` helper that logs via the
KMP-safe quartz Log when the file refuses to disappear.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-24 11:10:39 +02:00
davotoula 33dacaa260 fix(audio-room): give hand-raise button a visible toggled state
Replace FilledTonalIconButton with FilledTonalIconToggleButton so the
container color reflects raised/lowered state, and drop the tautological
`if (handRaised) PanTool else PanTool` conditional flagged by Sonar.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-24 11:09:14 +02:00
davotoula 59b36080ea docs: add SECURITY.md with private vulnerability reporting policy
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-24 09:54:12 +02:00
Vitor Pamplona 82bbfa3d94 Merge pull request #2541 from vitorpamplona/claude/fix-arrowback-icon-0v6aT
Optimize Material Symbols rendering with shared font and text measurer
2026-04-23 23:11:10 -04:00
Claude 72bef059f2 perf(icons): share FontFamily + TextMeasurer across Material Symbol icons
Material Symbol icons were allocating a fresh Font wrapper on every
composition (Font(resource = ...) returns a new instance each call),
which invalidated the remember(font) cache in rememberMaterialSymbolsFontFamily
and forced a fresh TextMeasurer per call site. Tint was also baked into
the TextStyle passed to measure(), so any tint change (selected /
unselected reaction icons, hover states) produced a cache miss.

Hoist the FontFamily + TextMeasurer to CompositionLocals provided once
at the root (AmethystTheme on Android, MaterialTheme wrapper on desktop)
via ProvideMaterialSymbols. Every icon in the subtree now reuses:

- One FontFamily (Skia typeface cache hit for every subsequent glyph)
- One TextMeasurer with a 64-entry LRU, shared across all call sites
  so its measurement cache is hit across 300+ MaterialSymbols usages
- Tint applied via drawText(layout, color = tint) instead of TextStyle,
  so the measured TextLayoutResult is reused across tint variations

Icon(symbol = ...) now delegates to Material3's Icon(painter = ...),
restoring proper sizing/semantics without the custom Box+drawBehind
workaround. autoMirror handled inside the painter's onDraw. The
previously-unused MaterialSymbolPainter is now the single render path.

The weight parameter is dropped from the Icon/Painter APIs since the
app uses a single weight globally (MaterialSymbolsDefaults.WEIGHT).

A local fallback path keeps @Preview composables that don't wrap in
the theme renderable (per-composition allocation, same cost as before).

https://claude.ai/code/session_01V9esGUFH72ujCQNWCAnjKN
2026-04-24 03:01:23 +00:00
Claude a436df7a21 Merge remote-tracking branch 'origin/main' into claude/fix-arrowback-icon-0v6aT 2026-04-24 02:44:48 +00:00
Claude 12a5926f6a refactor(notifications): centralize age + self gates, predicate observer
Audit of every notify() path found self-exclusion + 15-min age checks
duplicated across nearly all of them, with two inconsistencies (DM kind 4
and zap kind 9735 were missing explicit self-checks). Reactions, zaps,
and chess also re-ran isTaggedUser even though consumeFromCache already
routes by the same `p` tag.

Collapse the duplication into two layers:

- Observer layer (NotificationDispatcher): 15-min rolling age cutoff is
  now enforced by a predicate on LocalCache.observeNewEvents, before any
  account routing happens. The Nostr Filter grammar's `since` is a fixed
  value from dispatcher start; the predicate re-evaluates TimeUtils.
  fifteenMinutesAgo() per event so the window rolls forward as wall-clock
  time advances.

- Per-account layer (dispatchForAccount): right after the call/wake-up
  branch and the MainActivity.isResumed gate, drop events authored by
  the current account. Kept per-account (not observer-wide) because in a
  multi-account session account A's outgoing event legitimately becomes
  account B's incoming notification on the same device, so a device-wide
  author-exclusion set would swallow A→B.

Mechanically, NewEventMatchingFilter now takes an optional predicate so
observers can reject on fields the Filter grammar can't express (like a
moving `createdAt` cutoff). LocalCache.observeNewEvents adds a matching
overload that forwards it.

With the gates centralized, every notify() method drops its own age and
self-author checks (and for reaction/zap, the redundant isTaggedUser).
notifyWelcome keeps its own guards because it's dispatched directly
from processMarmotWelcomeFlow, bypassing the observer and dispatchForAccount.
Calls and wake-ups also bypass the shared gates by their short-circuit
return before the shared block.

https://claude.ai/code/session_01GQDJxiHPogdzCNhUBN7Pjc
2026-04-24 02:28:41 +00:00
Vitor Pamplona e2df0f036b Merge pull request #2540 from vitorpamplona/claude/review-wakeup-notification-tPUzb
Improve WakeUp event handling for multiple referenced events
2026-04-23 22:11:31 -04:00
Vitor Pamplona 5b1be8ded5 Maybe 300 weight is better for these fonts 2026-04-23 22:10:18 -04:00
Claude ae6ed9ecb2 chore: drop MAX_WAKEUP_REFS cap in wakeUpFor
Filter assemblers already handle batching and sizing; capping here adds
nothing.
2026-04-24 02:04:59 +00:00
Claude 6a9e0a79a6 fix(icons): render MaterialSymbol Icons that rely on defaultMinSize
The Canvas-based MaterialSymbol Icon collapsed to 0x0 whenever callers
didn't pass an explicit size modifier (e.g. ArrowBackIcon, ClearTextIcon).

Canvas is a Spacer + drawBehind, and Spacer's measure policy only uses
maxWidth/maxHeight when the incoming constraints are fixed. Inside an
IconButton (state layer 40dp, content constraints 0..40), defaultMinSize
only lifts minWidth/minHeight, leaving the constraints unfixed - so
Spacer picked 0x0 and the glyph never drew. Icons that passed
Modifier.size(N.dp) worked only because size(...) produces fixed
constraints.

Swap Canvas for an empty Box with drawBehind. Box's EmptyBoxMeasurePolicy
uses minWidth/minHeight, so defaultMinSize(24, 24) now takes effect when
no size modifier is supplied - matching Material3's own Icon behavior.

https://claude.ai/code/session_01V9esGUFH72ujCQNWCAnjKN
2026-04-24 02:00:11 +00:00
Claude 5b57f03b07 perf(notifications): narrow observer filter to p-tagged + since start
The dispatcher previously observed every new event whose kind fell in
NOTIFICATION_KINDS, which on a live feed is effectively every kind-1,
video, picture, long-form, reaction, etc. — the vast majority of which
got discarded at consumeFromCache when the tagged-user set didn't
intersect any saved account.

Rebuild the observer Filter off LocalPreferences.accountsFlow() with:

- tags = mapOf("p" -> <signer pubkeys hex>)  — match only events that
  p-tag one of our notifiable accounts, which is exactly the set
  consumeFromCache was going to route anyway
- since = <dispatcher start time>           — relay-style `limit: 0`
  so historical re-broadcasts from before this session don't retrigger

The job collects accountsFlow via collectLatest + distinctUntilChanged,
so login/logout/add-account events cleanly cancel the old observer and
resubscribe with the new pubkey set. allSavedAccounts() is called once
up-front to prime the backing StateFlow (which is null until first
suspend read). Kinds with the uppercase-P root-author marker (NIP-22
CommentEvent root authors) still reach us via the same lowercase-p
tag the reply builder also emits on direct replies.

https://claude.ai/code/session_01GQDJxiHPogdzCNhUBN7Pjc
2026-04-24 01:58:23 +00:00
Claude 9574f4b68d fix: align WakeUp handling with spec semantics (p-tags = authors)
A previous commit misread the spec: p-tags on a WakeUp identify the
AUTHORS of the referenced events (the people whose events are the
subject of the wake-up), not the recipients. The consumer's existing
npub-in-p-tag match is therefore correct — "is this logged-in account
the author of a referenced event?".

- Revert WakeUpEvent.build() back to notify(about.toPTag()) and replace
  the comment with a spec-accurate one.
- In wakeUpFor, source author pubkeys from event.authorKeys() (p-tags,
  canonical) first, merge in the e-tag author hints, and fall back to
  the WakeUp signer only when both are empty. Bound by MAX_WAKEUP_REFS.

computeReplyTo and the referenced-event fetch path remain untouched —
those fixes are orthogonal to the p-tag semantic.
2026-04-24 01:54:48 +00:00
Claude 3c6b2bec9f fix: make WakeUp events actually deliver notifications
The WakeUp notification path had three latent bugs that caused pushed
WakeUps to silently not deliver anything to the user:

- WakeUpEvent.build() tagged the about event's AUTHOR as the recipient
  (via EventHintBundle.toPTag()), so a WakeUp about a zap from Alice to
  Bob would p-tag Alice. The consumer matches recipients by p-tag, so
  Bob — the intended recipient — was filtered out. Forward the about
  event's audience (its own p-tags) instead.

- wakeUpFor() passed the WakeUp's own note to EventFinderQueryState. The
  finder's filterMissingEvents only queries for the note itself (already
  in cache) or its replyTo (empty because computeReplyTo had no WakeUp
  case). The referenced events were never REQ'd on relays. Link the
  referenced events via computeReplyTo and subscribe the finder on each
  referenced note directly so filterMissingEvents picks them up.

- UserFinder was subscribed against the WakeUp's own pubKey (typically a
  push bot), not the author of the event the notification is about.
  Pull author pubkeys from the e-tag author hint, falling back to the
  WakeUp signer only when the hint is absent.

Also: bound e-tag count per WakeUp to 16 to prevent subscription
flooding, log the 30s timeout, extract WAKEUP_WINDOW_MS constant, drop
the now-unused Note parameter from wakeUpFor.
2026-04-24 01:26:50 +00:00
Claude 1b24d02624 feat(notifications): route all content-event citations to Mentions
Extend the Mentions channel beyond kind-1 text notes to cover every
public content kind that can p-tag the user:

- PictureEvent (20)
- VideoNormalEvent (21), VideoShortEvent (22),
  VideoHorizontalEvent (34235), VideoVerticalEvent (34236)
- ChannelMessageEvent (42)
- PollEvent (1068)
- GitPatchEvent (1617), GitIssueEvent (1621)
- HighlightEvent (9802)
- LongTextNoteEvent (30023)
- WikiNoteEvent (30818)

NotificationDispatcher observes these kinds so LocalCache insertions
wake the consumer. dispatchForAccount routes all of them through a
shared catch-all branch to notifyMention, which was generalized from
TextNoteEvent to Event. The existing self-authored and 15-min filters
now live inside notifyMention so the catch-all branch doesn't have to
repeat them per kind.

Replies (kind 1 to our own notes, kind 1111 comments with us as root
or direct-reply author) still land on the Replies channel; all other
p-tag citations fall to Mentions.

https://claude.ai/code/session_01GQDJxiHPogdzCNhUBN7Pjc
2026-04-24 01:22:37 +00:00
Claude cc68c2ae1f feat(notifications): mentions channel, per-thread grouping, inline reply
- Split kind-1 routing in EventNotificationConsumer: if the reply target
  is authored by the current account it still lands on the Replies
  channel; any other p-tagged kind-1 (plain mention, citation, reply to
  someone else in a thread we're in) lands on a new dedicated Mentions
  channel. Users can disable either independently from Android settings.

- Group reply notifications by thread root. Compute the root via the
  NIP-10 marked/unmarked root markers for kind 1 and the NIP-22 root
  event/address tags for kind 1111, falling back to the direct parent.
  Use a per-thread group key + stable per-thread summary id so Android
  collapses "5 replies to your thread" into one entry instead of one
  big flat stack.

- Add an inline Reply action on reply notifications (kind 1 + kind 1111)
  using RemoteInput, mirroring the DM flow. NotificationReplyReceiver
  now handles PUBLIC_REPLY_ACTION: resolves the target event from
  LocalCache and signs a NIP-10 reply (TextNoteEvent.build with
  replyingTo hint) or a NIP-22 comment (CommentEvent.replyBuilder),
  broadcast via account.signAndComputeBroadcast. The relay proxy is
  held open for the duration via the same runOnRelay helper the DM
  flow uses.

https://claude.ai/code/session_01GQDJxiHPogdzCNhUBN7Pjc
2026-04-24 01:10:27 +00:00
Claude 6ad2135375 fix: unwrap GiftWraps for all writable accounts when always-on is enabled
When the always-on notification service is on, preload every saved writable
account into AccountCacheState so each one's newNotesPreProcessor subscribes
to LocalCache.live.newEventBundles. Without this, relay-delivered GiftWraps
addressed to non-active accounts would sit in LocalCache undeconstructed,
and NotificationDispatcher never fires for them.

The preload observes LocalPreferences.accountsFlow so accounts added during
a session (login flow) join automatically. On disable, the collector is
cancelled and every cached account except the active one is released —
users with the setting off keep today's single-account battery footprint.

Loading an account is idempotent (AccountCacheState checks cache first),
so this cannot double-init or conflict with PushWrapDecryptor's on-demand
loadAccount calls. Loading does not affect the active account selected by
AccountSessionManager, so the UI is unaffected.
2026-04-24 00:50:29 +00:00
Claude a9e0e0fe3f feat(notifications): notify on replies (NIP-10) and comments (NIP-22)
Adds a dedicated "Replies" notification channel that fires when someone
replies to one of the user's posts. Handled on two kinds:

- TextNoteEvent (kind 1): notifies only when the reply target
  (`replyingTo()`) is authored by the current account, so plain p-tag
  mentions are not routed through this channel.

- CommentEvent (kind 1111): notifies when the current account is the
  NIP-22 reply author or root author of the parent scope.

The channel uses its own group key and summary so users can disable it
independently from OS-level notification settings, just like Zaps,
Reactions, DMs, and Chess.

https://claude.ai/code/session_01GQDJxiHPogdzCNhUBN7Pjc
2026-04-24 00:48:31 +00:00
Vitor Pamplona 81819b6fbe updates kotlin file 2026-04-23 19:31:30 -04:00
Vitor Pamplona 75fb4845e5 Update AGP and dependencies 2026-04-23 19:15:12 -04:00
Vitor Pamplona 9db1353fbf Fixes test cases after moving files 2026-04-23 19:14:42 -04:00
Vitor Pamplona 9b5c8fe646 Merge pull request #2537 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2026-04-23 19:03:28 -04:00
Crowdin Bot 0017d01685 New Crowdin translations by GitHub Action 2026-04-23 23:02:10 +00:00
Vitor Pamplona 9854ccec1e Merge pull request #2536 from vitorpamplona/claude/review-marmot-implementation-BSrzC
Add leave/rejoin, reactions, deletions, and Marmot reset functionality
2026-04-23 19:00:24 -04:00
Vitor Pamplona d9b156d413 Merge pull request #2535 from vitorpamplona/claude/fix-banner-positioning-susFe
Improve broadcast UI styling with better spacing and rounded corners
2026-04-23 18:59:11 -04:00
Claude 655e56791b Merge branch 'main' into claude/review-marmot-implementation-BSrzC
# Conflicts:
#	amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/AllSettingsScreen.kt
#	cli/src/main/kotlin/com/vitorpamplona/amethyst/cli/commands/Commands.kt
2026-04-23 22:36:29 +00:00
Vitor Pamplona 6ac6d84cb1 Merge pull request #2530 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2026-04-23 18:31:40 -04:00
Claude c0e5cc617e Merge remote-tracking branch 'origin/main' into claude/fix-banner-positioning-susFe
# Conflicts:
#	amethyst/src/main/java/com/vitorpamplona/amethyst/ui/broadcast/BroadcastBanner.kt
2026-04-23 22:28:31 +00:00