Commit Graph

13575 Commits

Author SHA1 Message Date
Crowdin Bot a42e759d01 New Crowdin translations by GitHub Action 2026-05-14 19:23:21 +00:00
Crowdin Bot 961cce7f18 New Crowdin translations by GitHub Action 2026-05-14 18:54:14 +00:00
davotoula 7dbb78644a i18n: translate compose/profile-UI/security/nest strings into cs/de/pt-BR/sv
Adds Czech, German, Brazilian Portuguese, and Swedish translations
for 19 new keys covering auto-create-drafts compose settings,
profile-UI section toggles, security-screen empty states and
section headers, external-resource comment scopes, and nest
host-action failure toasts.
2026-05-14 20:50:48 +02:00
Vitor Pamplona 08adffb862 Merge pull request #2897 from vitorpamplona/claude/drag-drop-corner-icon-EoFbd
Move drag gesture detection to icon Box in VideoPlayerButtonItemCard
2026-05-14 14:46:43 -04:00
Vitor Pamplona 35be1aafd4 Merge pull request #2896 from vitorpamplona/claude/enable-client-tag-security-filters-i0rRK
Invert client tag setting: add instead of disable
2026-05-14 14:45:43 -04:00
Vitor Pamplona 06f3a6bc85 Merge pull request #2895 from vitorpamplona/claude/fix-stage-position-swap-4f6nI
Remove speaker reordering in stage grid to prevent UI shuffle
2026-05-14 14:43:41 -04:00
Claude 35dfa119c3 fix: restrict video player button reordering drag to the drag handle
Move the detectDragGestures pointerInput from the whole button card to
the six-dot drag handle icon so drag-and-drop reordering activates only
when the user grabs the handle.

https://claude.ai/code/session_01Jyu8dLYN7MXVNaVZE3u2M9
2026-05-14 18:41:18 +00:00
David Kaspar 467028138c Merge pull request #2892 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2026-05-14 20:37:35 +02:00
Claude 2408c43918 fix: stop reordering Nest stage members by speaking state
Members were sorted to float the current speaker to the top, so a
member jumped to the front when they started talking and slid back
~250ms after they stopped — a constant shuffle in any active room.
The speaking state is already shown in-place via the mic badge and
audio-level ring, so keep members in their stable arrival order.

https://claude.ai/code/session_01GvvNS1FRn6gGvHRC7dgEtP
2026-05-14 18:35:40 +00:00
Crowdin Bot 87b6bae6da New Crowdin translations by GitHub Action 2026-05-14 18:34:30 +00:00
Vitor Pamplona be891cb828 Merge pull request #2894 from vitorpamplona/claude/review-appimage-structure-193mv
Move packaging/appimage to desktopApp/packaging/appimage
2026-05-14 14:32:52 -04:00
Claude 1a8e349e9f feat(privacy): enable NIP-89 client tag by default and move to Compose settings
Convert the per-account `disableClientTag` toggle into a positive
`addClientTag` setting that defaults to enabled, and relocate it from the
Security Filters screen to the Compose Settings page.

https://claude.ai/code/session_01TkmBzzEz3kKnrjpbrKCpsf
2026-05-14 18:29:28 +00:00
Vitor Pamplona d8d077b4e7 Merge pull request #2893 from vitorpamplona/claude/add-nav-padding-J2XWn
Fix IME padding in HiddenWordsScreen using WindowInsets
2026-05-14 14:23:14 -04:00
Claude 3ff721b31a refactor: move packaging/appimage into desktopApp module
The AppImage build inputs (AppRun, .desktop entry, icon, and the
CI-fetched linuxdeploy binary) are consumed only by desktopApp's
createReleaseAppImage task. Co-locating them under
desktopApp/packaging/appimage/ removes the `../` path escape from the
build script and keeps all desktop packaging assets inside the module.

https://claude.ai/code/session_0137ULcfJkASmfmffFBdW8ac
2026-05-14 18:21:28 +00:00
Claude 8df02cf8f8 fix: pad Hidden Words screen for bottom nav and gesture bars
The bottom add-word text field sat behind the phone's navigation
and gesture bars. Apply the union of navigation bar and IME insets
to the bottom bar so it clears the system bars and rises with the
keyboard without a double-counted gap.

https://claude.ai/code/session_01KPkQbS9SYTigkkzYGU97Sz
2026-05-14 18:21:12 +00:00
Vitor Pamplona b385088c95 more lints 2026-05-14 13:22:03 -04:00
Vitor Pamplona 76c7c1575e removes incorrect test 2026-05-14 13:13:51 -04:00
Vitor Pamplona 4a71d890cb Merge branch 'main' of https://github.com/vitorpamplona/amethyst 2026-05-14 13:13:39 -04:00
Vitor Pamplona b28f0d246d Overriding the new PiP function 2026-05-14 12:17:09 -04:00
Vitor Pamplona 5d060899c2 additional linting 2026-05-14 12:11:39 -04:00
Vitor Pamplona d60dcf9c79 update dependencies 2026-05-14 12:06:55 -04:00
Vitor Pamplona 93c6010e19 liniting 2026-05-14 12:05:17 -04:00
Vitor Pamplona 0a330f18b0 Merge branch 'main' of https://github.com/vitorpamplona/amethyst 2026-05-14 11:54:16 -04:00
Vitor Pamplona 449fbb2f79 Merge pull request #2891 from vitorpamplona/claude/comment-reply-context-XMBOV
Display external identifier scopes in NIP-22 comments
2026-05-14 11:46:19 -04:00
Claude b7ead41145 fix: use lambda for generic RootIdentifierTag.match call
RootIdentifierTag is generic, so the `::match` method reference failed
JVM compilation. Call it through a lambda like the rest of CommentEvent.

https://claude.ai/code/session_01ArHkNXu1ANrVGZAyMWg4Xu
2026-05-14 15:28:53 +00:00
Claude a690777ef6 feat: show external-id scope as reply context for NIP-22 comments
A Comment event (kind 1111) scoped to an external identifier (`I` tag)
previously rendered as a bare text post and landed in the Home "New
Threads" feed. Treat it as a reply to that external scope: it now shows
in the conversations feed and renders a typed chip (hashtag, geohash,
url, or generic) above the comment text.

https://claude.ai/code/session_01ArHkNXu1ANrVGZAyMWg4Xu
2026-05-14 15:05:04 +00:00
Vitor Pamplona 47a5f19050 Removes unneeded strings 2026-05-14 10:31:45 -04:00
Vitor Pamplona f7f4107ad7 Merge pull request #2886 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2026-05-14 10:00:28 -04:00
Crowdin Bot 1c59849fe4 New Crowdin translations by GitHub Action 2026-05-14 13:38:40 +00:00
Vitor Pamplona 56ca4214fe Merge pull request #2890 from vitorpamplona/claude/standardize-nav-behavior-cA6mg
Add bottom navigation bar to ScheduledPostsScreen
2026-05-14 09:36:56 -04:00
Claude 2a5e95f4cf fix: standardize ScheduledPosts nav-shell behavior
ScheduledPostsScreen used a plain Scaffold with no bottom bar and an
unconditional back arrow, so a user who pinned it to the bottom nav got
a back arrow and no bottom bar instead of the standard tab-root shell.

Add AppBottomBar(Route.ScheduledPosts) and gate the back arrow on
nav.canPop(), matching every other bottom-nav-eligible screen: back
arrow + no bottom bar when entered from the drawer, no back arrow +
bottom bar when entered from the bottom nav.

https://claude.ai/code/session_01WZXxqCGYT4JEQBwwBNiUjm
2026-05-14 13:26:47 +00:00
Vitor Pamplona fe9764072f Merge pull request #2887 from vitorpamplona/claude/add-zap-button-nest-KoZG4
feat(nests): add a zap button to the room action bar
2026-05-14 09:21:46 -04:00
Claude 4a3cea85cb fix(nests): clear zap spinner on wallet handoff; move overlay off the hand badge
NestZapButton had no ObserveZapIconState fallback (unlike NoteCompose's
ZapReaction), so after onPayViaIntent handed the invoice to an external
wallet the progress spinner stayed up indefinitely in the long-lived
NestActivity. Reset zappingProgress to 0 on every onPayViaIntent path.

The floating zap overlay was anchored TopEnd, colliding with the
hand-raise badge — and since the merge made zaps float from the
zapper's avatar (a likely hand-raiser), that overlap would be common.
Moved it to TopCenter, the only badge-free anchor.

Also dropped dead state (zapStartingTime / the non-animated
animatedProgress alias) and corrected stale comments left over from
the sender-grouping merge.

https://claude.ai/code/session_01EW11kUdiEYPuPti7vtD2AR
2026-05-14 13:19:44 +00:00
Vitor Pamplona f19964da81 Merge pull request #2889 from vitorpamplona/claude/review-skills-library-86OMY
Add technique-layer Compose and Kotlin skills
2026-05-14 09:11:10 -04:00
Claude f45479615e Merge remote-tracking branch 'origin/main' into claude/add-zap-button-nest-KoZG4
# Conflicts:
#	amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/nests/room/screen/NestActionBar.kt
#	amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/nests/room/stage/ParticipantsGrid.kt
2026-05-14 13:08:13 +00:00
Claude f5354c2d34 docs: forbid inline fully-qualified class names in Kotlin
Adds a Kotlin style rule to CLAUDE.md requiring imports over
fully-qualified class names inline in function bodies.

https://claude.ai/code/session_01QE8CjoJXUt7RKtwGgzeMrb
2026-05-14 13:07:52 +00:00
Vitor Pamplona 82218bf73c Merge pull request #2888 from vitorpamplona/claude/fix-disk-space-usage-vBWWv
Defer image cache eviction unlinks to background thread
2026-05-14 09:03:06 -04:00
Claude 5177168583 docs(skills): vendor 10 Compose/Kotlin technique-layer skills
Adds technique-oriented Compose and Kotlin skills from chrisbanes/skills
to complement the existing codebase-oriented skills. Codebase skills cover
"where is X in Amethyst"; these cover "what is the correct Compose/Kotlin
design". Descriptions tagged as technique-layer and cross-links trimmed to
the vendored subset. CLAUDE.md skill table updated with a dedicated section.

https://claude.ai/code/session_01QE8CjoJXUt7RKtwGgzeMrb
2026-05-14 12:59:46 +00:00
Claude 0e1d06cb06 test: verify Coil eviction routes through DeferredDeleteFileSystem; correct KDoc
Decompiled Coil 3.4.0's DiskLruCache to verify the assumption the wrapper
rests on. The original "inline inside commit()" framing was imprecise:
completeEdit() calls launchCleanup(), which launches on a limited-parallelism
IO scope — eviction is not on the commit thread. But the cleanup coroutine
runs trimToSize() *while holding the global DiskLruCache lock*, and
trimToSize -> removeEntry -> fileSystem.delete() does the unlink syscalls
under that lock. openSnapshot() (read) and openEditor() (write) both contend
on the same lock, so a cleanup pass blocks all feed-scroll reads/writes for
the duration of a burst of delete() syscalls. The wrapper still targets
exactly the right call; the mechanism is lock-holding, not thread-stealing.

- Correct the DeferredDeleteFileSystem KDoc to describe the verified
  lock-holding mechanism.
- Add DeferredDeleteFileSystemCoilIntegrationTest: drives the real Coil 3
  DiskCache over the wrapper, saturates it past maxSizeBytes, and asserts
  eviction's unlinks land in the wrapper's queue (pendingCount > 0) with the
  files still physically on disk until drained — the empirical complement to
  the bytecode reading. A second test exercises the re-fetch-after-eviction
  race against real Coil. Pins the assumption: a future Coil that stops
  deleting via the injected FileSystem fails this test instead of silently
  turning the wrapper into a no-op.
2026-05-14 12:51:51 +00:00
Claude 524bc90d5a test(nests): cover RoomZapsAggregator; tidy zap-overlay comments
Adds RoomZapsStateTest mirroring RoomReactionsStateTest (grouping,
eviction, room-wide keying, idempotent snapshots, dedup). Also drops
the unused RoomZapsAggregator.isEmpty(), pins the zap chip's content
color to white since BitcoinOrange is theme-independent, and corrects
a few doc comments that overstated component/timer sharing.

https://claude.ai/code/session_01EW11kUdiEYPuPti7vtD2AR
2026-05-14 12:49:18 +00:00
Vitor Pamplona 0182439b55 fix NestViewModelTest for sender-grouped reaction aggregator
onReactionEventGroupsByTargetAndEvictsOnTick asserted the pre-change
targetPubkey grouping (recentReactions.value[bob]); the aggregator
now groups by sourcePubkey so the chip rises from the reactor's
avatar. Renamed to ...GroupsBySender and assert under alice's key.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-14 08:45:21 -04:00
Vitor Pamplona 78be534df5 nests room: stable reactions overlay + repeatable kind-7 + speaker controls
Reaction-side rework after extended testing on 2026-05-13:

* Promoted users weren't seeing speaker controls in their own
  Amethyst — `StageControlsBar` gates on `isOnStage && ui.onStageNow`
  and `ui.onStageNow` was stuck at false (never reset back to true
  after some earlier path flipped it). Added a LaunchedEffect in
  NestActivityContent that mirrors `isOnStageMe → ui.onStageNow`
  whenever it becomes true, symmetric to the auto-stop effect.

* Reactions overlay sat inside AvatarAndBadges' wrap-content Box,
  so adding/removing the chip shifted the inner Box's centre and
  the role badges drifted. Lifted the overlay out to be a sibling
  of AvatarAndBadges inside the outer fixed-size Box; badges now
  stay anchored regardless of chip presence or animation state.

* Reaction grouping was by `targetPubkey` (NIP-25 p-tag) — emojis
  showed on the speaker being reacted to, not the reactor. Switched
  RoomReactionsAggregator to group by `sourcePubkey`. AudienceGrid
  threads `reactionsByPubkey` so an audience reactor sees their own
  emoji float from their audience-tab avatar too.

* Reaction chip animation: progress was an `Animatable.value` that
  Compose wasn't refreshing in the layout-consuming layer (visible
  bug: chip "blinked" with no movement). Replaced with a manual
  `withFrameNanos` loop writing to a `MutableFloatState` read inside
  a `graphicsLayer { … }` lambda — frame-clock animation that works.
  Each kind-7 is its own chip keyed by event-id (no more
  groupBy-content collapsing same-emoji bursts into one shared chip
  that restarts on every arrival). Chips stack at a fixed-size 30 dp
  Box with the emoji centred, so the X position is invariant under
  glyph width. Multiple concurrent chips overlap at the right-bottom
  corner in a `Box(BottomEnd)` (newest on top) rather than sliding
  leftward in a `Row`.

* Bottom-drawer `RoomReactionPickerSheet` (hard-coded 6 emojis, no
  NIP-30) replaced by a forked `RoomReactionPopup` that reuses
  `ReactionChoicePopupContent` (same NIP-30 custom-emoji support,
  same user-configured reaction set) but with two semantic deviations
  for live audio rooms: empty `toRemove` so all buttons stay
  "fresh", and the click handler signs+broadcasts a fresh kind-7
  template directly instead of going through `Account.reactTo` —
  which delegates to `ReactionAction.reactTo(note, …)` and
  short-circuits on `note.hasReacted(by, reaction)`. The bypass
  lets the user fire the same emoji repeatedly during a moment.

* Chip rendering matches NoteCompose: `RenderReactionContent`
  handles `:shortcode:url` via `InLineIconRenderer` + `CustomEmoji`,
  `"+"`→❤️, `"-"`→👎, anything else as Text.

Tests updated for sender-grouped aggregator semantics.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-14 08:45:21 -04:00
Vitor Pamplona d21eb0e226 fix nests room host UX: visible toasts, promote propagation, edit-sheet keyboard, reaction animation
This commit bundles several issues surfaced while testing host actions
inside an audio room.

NestActivity didn't mount `DisplayErrorMessages`, so every toast emitted
by nest code (promote, demote, kick, force-mute, errors) queued into a
StateFlow whose only collector lives in MainActivity. Mounted it inside
NestActivity.setContent so toasts now render in front of the room UI —
same way the leave-confirmation AlertDialog already does.

Host actions used to fire a synchronous "Promoted X" toast regardless of
whether `signAndComputeBroadcast` actually completed. Silent signer
failures (TimedOut, ManuallyUnauthorized, CouldNotPerform, etc. — all
Log.w-only in launchSigner) were invisible from the host's POV. Replaced
with a coroutine-bound failure toast that surfaces the exception class
+ message; success is implicit via the UI update.

The real cause of "promoted user stays in audience tab" turned out to be
stale presence: a kind-10312 emitted before the role grant (with
onstage=0) was pinning the freshly-promoted speaker to the audience tab.
buildParticipantGrid now takes a `roleGrantSec` parameter (the
kind-30312 created_at) and treats presence as authoritative only when
strictly newer. Pinned with two new tests covering both the
stale-ignore and fresh-respect cases. The leave-stage-on-another-client
flow keeps working because that emits a fresh onstage=0.

RoomParticipantActions.rebuild now uses
`(original.createdAt + 1L).coerceAtLeast(now())` so a same-second
promote→demote can't tie-break the wrong way under NIP-01's lowest-id
rule.

EditNestSheet's bottom row got squashed when the keyboard appeared —
the form fields couldn't shrink, so the Save/Cancel/Close row took the
hit. Split into a scrollable form column + sticky action row, wrapped
the outer column in imePadding.

SpeakerReactionOverlay was driving drift on a 100 ms `delay` loop over
the 10 s eviction window — produced 0.16 dp per tick (visibly stepped)
and the chip barely moved before disappearing. Replaced with
`Animatable.animateTo` on Compose's frame clock and a 6 s duration so
the chip pops, lingers visibly, then drifts up and fades.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-14 08:45:20 -04:00
Vitor Pamplona 82ce4df201 Merge pull request #2880 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2026-05-14 08:38:13 -04:00
Vitor Pamplona 38b5b677e8 Merge pull request #2885 from vitorpamplona/claude/add-audio-playback-f4a-FZy9V
feat: add audio playback support for .f4a files
2026-05-14 08:38:04 -04:00
Claude b6c6c0d2a2 perf: defer Coil disk-cache eviction unlink() off the write path
Coil's DiskLruCache runs trimToSize() inline inside commit(): once the
image cache is saturated, every entry written during a feed scroll fires
a burst of synchronous delete() syscalls on the same thread, contending
with the reads that paint on-screen avatars — the scroll stall users see
go away after a storage wipe.

DeferredDeleteFileSystem is an okio ForwardingFileSystem handed to
DiskCache.Builder.fileSystem(). It intercepts delete() — exactly the call
eviction makes — enqueues the path, and unlinks it on a background
coroutine one path at a time with a yield() between each, keeping the IO
dispatcher responsive. Coil keeps full ownership of LRU order, keys, and
size accounting; only the syscall is rescheduled.

The re-create race (Coil re-fetching a URL whose entry was just evicted)
is handled by atomicMove/sink/appendingSink/openReadWrite/createDirectory
cancelling any pending delete of the path they are about to recreate. The
drainer unlinks each path under the same lock, so a concurrent write-path
call waits at most one in-flight unlink — O(1), never the O(N) burst.

This replaces the reverted KeyAccessLog/TrackingDiskCache/CoilDiskTrimmer
approach: no parallel LRU index, no persistent log, no extra heap — it
intercepts at the eviction syscall itself and leans on Coil's own LRU.

12 unit tests cover deferral, drain, dedup, the cancel-on-recreate paths
for every write entry point, pass-through of non-delete ops, and the
background drainer including a burst-then-recreate race.
2026-05-14 11:14:12 +00:00
Claude 472367b4ff Revert "perf: drain Coil disk cache off the write path to prevent scroll stalls"
This reverts commit b221dce8.

The KeyAccessLog/TrackingDiskCache/CoilDiskTrimmer approach rebuilt, persistently,
the LRU index Coil already keeps in memory (~50 MB heap for the key map), and the
trimmer drained the cache in one unbounded delete burst rather than spreading the
work — potentially a worse stall than the inline eviction it replaced, just less
frequent.

Replacing it with a FileSystem-layer delegate that defers the eviction unlink()
off the commit thread without any parallel bookkeeping.
2026-05-14 11:05:13 +00:00
Claude 773d61692e feat: add audio playback support for .f4a files
F4A is AAC audio in an MP4 container (Adobe's variant of .m4a).
Register the extension in the media URL list so it routes to the
player, and map it to audio/mp4 so ExoPlayer picks the MP4 extractor.

https://claude.ai/code/session_01EuB46tfwBxtvRNDz9Ju99J
2026-05-14 10:56:45 +00:00
Claude 6425fcd651 feat(nests): add zap button to the full-screen action bar
Subscribes to kind-9735 zap receipts tagged with the room's a-pointer,
feeds them into the nest chat ledger (so RenderChatZap surfaces them
the same way live streams do) and into a sliding-window aggregator
that drives a floating  chip over the targeted participant's avatar
— mirrors the existing reaction overlay's animation cadence so both
streams visually feel like one system.

The button itself wraps NoteCompose's zapClick / ZapAmountChoicePopup
/ ZapCustomDialog defaults against the room's AddressableNote, so the
amount-choice popup, custom-amount dialog and multi-payable routing
all behave like the standard note  button.

https://claude.ai/code/session_01EW11kUdiEYPuPti7vtD2AR
2026-05-14 04:43:25 +00:00
Claude b221dce87f perf: drain Coil disk cache off the write path to prevent scroll stalls
When Coil's DiskLruCache is saturated, every commit() triggers an inline
trimToSize() that performs file deletes synchronously on the IO dispatcher.
With Nostr feed scroll loading many large source avatars, each new write
evicts dozens of small entries, contending with concurrent reads of the
on-screen images and producing visible avatar-load latency and scroll jank.

Take ownership of eviction so size stays < maxSize during normal use:

- TrackingDiskCache wraps the real Coil DiskCache and records every key
  access (openEditor/openSnapshot) into a persistent log.
- KeyAccessLog persists (timestamp, key) pairs in an append-only file under
  filesDir, replayed on startup. Bounded at 200K entries with periodic
  compaction.
- CoilDiskTrimmer runs on a 5-minute cadence and on onTrimMemory: when size
  crosses 70% of maxSize it removes oldest keys via the public remove(key)
  API until size drops to 55%. Coil's internal trim remains as a backstop
  for keys we don't yet know about.

Unit tests cover persistence, idempotent load, corrupt-line recovery,
overflow handling, target/floor draining, oldest-first ordering, empty-log
fallback, and concurrent-call serialisation.
2026-05-14 04:26:20 +00:00