Commit Graph

12626 Commits

Author SHA1 Message Date
Crowdin Bot 3bfd49b94f New Crowdin translations by GitHub Action 2026-04-28 15:25:12 +00:00
Vitor Pamplona 090f7499ae Merge pull request #2624 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2026-04-28 11:23:44 -04:00
Vitor Pamplona b5c19f1320 Merge pull request #2625 from vitorpamplona/claude/review-pip-transition-ui-2UYbc
Enhance Nest PIP with dynamic speaker focus and connection status
2026-04-28 11:22:50 -04:00
Claude 4c63e58e68 feat(nests): PIP focuses on active speakers, surfaces self + connection state
Pulls three high-signal features from NestScreen into the PIP layout
without crowding the small floating window:

- Audio-level pulsing rings on active speakers, mirroring the
  full-screen StageGrid contract. The ring width tracks live audio
  level so the PIP visibly responds to who is talking, instead of
  showing static borders.
- Adaptive speaker selection: when anyone is speaking, PIP shows only
  the active speakers (up to 4); when exactly one person is talking
  the avatar bumps to 56dp for a clear focus mode. With no one
  speaking, falls back to the first 4 on-stage participants so the
  PIP isn't blank during silence. Fixes the 'same 4 faces while
  someone else talks off-screen' problem in big rooms.
- Self-status row (mic broadcasting / muted, hand-raised), so a
  glance at the PIP answers 'am I muted?' without expanding back.
  Hidden for pure audience listeners with no hand to keep clutter low.
- Reconnecting / Failed caption over a dimmed body so audio drops
  are obvious — previously the avatars stayed lit even with no audio
  arriving.

https://claude.ai/code/session_01BpwAxN9gs79CJuWKqf3BEB
2026-04-28 15:17:44 +00:00
Claude 851045c654 fix(nests): listener subscriptions survive publisher recycle
Two layered fixes for the gap captured in
nestsClient/plans/2026-04-28-listener-survives-publisher-recycle.md.

Session layer (MoqLiteSession.kt):
  - Lazy single shared announce-watch pump per session, opened on
    first subscribe. moq-lite Lite-03 has no explicit "publisher
    gone" message on the subscribe bidi (the relay keeps that
    bidi open across publisher cycles in case a fresh publisher
    takes over the suffix), so the announce stream's Ended event
    is the only reliable signal.
  - On Announce(Ended) for a broadcast suffix, close the
    matching ListenerSubscription's frames Channel and remove it
    from the map. The wrapper-level frames.consumeAsFlow() flow
    ends naturally — same shape as a user-driven
    handle.unsubscribe() — so the wrapper pump's collect-
    completion path drives the re-issue.

Wrapper layer (ReconnectingNestsListener.kt):
  - Inner re-subscribe `while (currentCoroutineContext().isActive)`
    loop in reissuingSubscribe. When the underlying frames flow
    completes (publisher cycled, signalled by the session layer
    above), re-issue subscribe against the same listener with a
    100 ms backoff. moq-lite supports subscribe-before-announce so
    a re-subscribe issued during the gap attaches cleanly when
    the next publisher comes up under the same suffix.

Verified against the real moq-rs relay (host build, external
mode): the new
NostrNestsReconnectingListenerInteropTest.subscribe_handle_survives_publisher_recycle
test passes — single SubscribeHandle keeps emitting frames across
multiple speaker JWT-refresh cycles. Speaker reconnect tests
still pass too.

In production, this closes the audio dropout that would have
fired every 9 minutes per speaker JWT refresh on long Nest calls
(see the plan doc for the original diagnosis).

https://claude.ai/code/session_01HXf3zG3F2ev2ASeQju7Y5S
2026-04-28 15:06:07 +00:00
Claude 4756348221 feat(nests): back gesture and Minimize button enter PiP instead of leaving
The only way to keep audio playing while leaving the room screen was the
Home/Recents gesture (onUserLeaveHint). Back swiped the user out of the
session entirely, and there was no in-UI affordance signalling that PiP
existed. Users wanting to step away had no obvious path that preserved
audio.

- BackHandler in NestActivityContent: while connected and PIP is
  supported, back minimizes to PIP. Disconnected/Connecting/Failed
  fall through to the default finish() so cancelling still works.
- Minimize IconButton in the room TopAppBar (hidden when the device
  doesn't advertise FEATURE_PICTURE_IN_PICTURE).
- Public NestActivity.enterPip() consolidates the connected + feature
  guards so onUserLeaveHint, BackHandler, and the Minimize button all
  share one entry point.

https://claude.ai/code/session_01BpwAxN9gs79CJuWKqf3BEB
2026-04-28 15:03:34 +00:00
Crowdin Bot 33dccc1d7a New Crowdin translations by GitHub Action 2026-04-28 14:35:17 +00:00
Vitor Pamplona 30e34a408a Merge pull request #2623 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2026-04-28 10:33:15 -04:00
Vitor Pamplona db7932baac Merge pull request #2620 from vitorpamplona/claude/improve-participate-grid-FVS9L
feat(nests): bigger avatars + clearer mute/speaking state in participant grid
2026-04-28 10:32:56 -04:00
Claude a0c7889183 feat(nests): beauty pass on the participant grid
Seven coordinated visual changes so the grid feels less prototype-y
and more native to the user's Material 3 theme:

#1 Colour harmonisation. Drops the hardcoded Tailwind hexes for
   host (purple-500), moderator (sky-500) and hand-raise (yellow-500)
   in favour of MaterialTheme.colorScheme tones — host → primary,
   moderator → secondary, hand → tertiary, each paired with the
   matching onContainer for the icon tint. Speaking-green is kept as
   a hardcoded brand colour because "green = mic active" is a
   universal convention (Spaces, Clubhouse, Discord); a primary tint
   would lose that signal.

#2 Stage strip as a tonal card. StageGrid now wraps its label + grid
   in a Surface(color = surfaceContainerLow, shape = 20.dp rounded)
   so the active speakers visually live in their own zone, separated
   from the chat / audience tab below.

#3 Breathing room. GRID_SPACING goes from 6.dp to 8.dp — still keeps
   4 cells per row on a 411.dp Pixel (4*96 + 3*8 = 408 ≤ 411). The
   stage card adds 12.dp internal padding.

#4 Empty-stage hint with icon. Adds an HourglassEmpty glyph above
   the existing "Waiting for speakers…" copy so the empty state reads
   as intentional rather than as a layout glitch.

#5 Ring color crossfade. animateColorAsState on the speaking-ring
   color in addition to the existing animateDpAsState on width — the
   transition from idle → speaking → muted → idle no longer snaps.

#6 Soft outer halo. New drawBehind layer paints a transparent
   speaking-green circle scaled by the live audio level. Uses
   drawBehind so the glow extends beyond the avatar's layout bounds
   without affecting cell measurement; alpha animates to 0 when the
   speaker stops, so it dims rather than disappears.

#7 Reaction chips. SpeakerReactionOverlay wraps in AnimatedVisibility
   with fade + scale-in/out so a 👏 burst pops in and dims out
   instead of snapping. Chips swap secondaryContainer fill for a
   tonal Surface (tonalElevation 2dp + shadowElevation 1dp) — softer
   in light mode, properly tinted in dark mode.
2026-04-28 14:26:40 +00:00
Claude 1c8c961762 docs(nests): plan for listener-survives-publisher-recycle gap
Discovered while validating connectReconnectingNestsSpeaker against
the real moq-rs relay: when a publisher cycles its session (e.g. via
JWT refresh), an existing listener-side SubscribeHandle does not
auto-reattach to the new publisher under the same broadcast suffix.
Frames stop until the listener's own JWT refresh fires.

Root cause is multi-layer:
  - moq-lite session: SubscribeHandle.frames is a
    Channel.consumeAsFlow() that the session never closes on remote
    disconnect.
  - moq-lite Lite-03: graceful close emits Announce(Ended), which
    the wrapper would need to observe to react.
  - ReconnectingNestsListener: reissuingSubscribe only re-issues on
    listener-session swaps, not on announce-Ended.

A wrapper-layer announce-driven re-subscribe was attempted (race
collect vs announce-Ended.first(), cancel loser, unsubscribe, retry).
It compiled and the unit tests passed, but interop against the real
relay still showed listener silence after the recycle — the listener's
QUIC session terminated 4 ms after the publisher's "subscribe
cancelled" log line, suggesting the announce-bidi cleanup or
sub-unsubscribe path is being interpreted as session close at the
moq-lite layer. Did not pin down the exact chain.

Reverted the wrapper change to keep the branch clean. Speaker-side
connectReconnectingNestsSpeaker is unaffected and continues to pass
its interop tests. Plan doc captures the diagnosis and three
candidate fix paths for follow-up — preferred direction is
session-layer (have MoqLiteSession close the frames Channel when the
relay forwards Announce(Ended) or FINs the subscribe bidi) so the
existing wrapper pump's natural collect-completion handles it.

https://claude.ai/code/session_01HXf3zG3F2ev2ASeQju7Y5S
2026-04-28 13:29:10 +00:00
Claude ae2bae13ac feat(nests): self highlight, floating reactions, kick drops p-tag, tap-self mute
#1 Local user's cell tints the username in colorScheme.primary so you
   can find yourself in a busy stage. Threads myPubkey through StageGrid
   and AudienceGrid; cells receive isSelf and pass MaterialTheme primary
   to UsernameDisplay's textColor.

#2 SpeakerReactionOverlay moves from below the username (which reflowed
   the column when reactions arrived and shoved neighbors down) to
   inside the avatar Box, anchored bottom-end with a small outward
   offset so it overlaps the corner without colliding with the
   bottom-center mic badge. Cell height stays constant whether the
   speaker has 0 or 5 active reactions.

#3 Kick now mirrors nostrnests' two-step path: the existing ephemeral
   kind-4312 ['action','kick'] kicks the target off the audio plane,
   followed by a re-published kind-30312 with their p-tag dropped so
   the participant grid stops rendering them. New
   RoomParticipantActions.removeParticipant builds the second event;
   refuses to drop the host (same protection as setRole demoting host).

#7 Tap-to-mute on your own avatar. Self-cell receives onTapSelf which,
   when broadcasting, toggles BroadcastUiState.Broadcasting.isMuted via
   the existing setMicMuted path. Falls back to no-op when not
   broadcasting so a non-broadcasting tap doesn't surface a useless
   action — the avatar simply isn't tappable.
2026-04-28 13:21:21 +00:00
Claude 52a506dd92 test(nests): scope speaker-refresh interop to speaker invariants only
The first cut of the JWT-refresh interop test held a single listener-
side SubscribeHandle open across the speaker's session recycle and
asserted both pre- and post-recycle frames arrived on it. That fails
because moq-lite's relay doesn't auto-route a vanilla SubscribeHandle
to a fresh publisher session under the same suffix — the
listener-survival-across-publisher-recycle is a separate concern,
NOT a speaker-reconnect bug. Verified against a real moq-rs relay
(host-built, external mode, --auth-key-dir + per-kid JWK file).

Reworked to validate just what the speaker reconnect should
guarantee:
  - Phase 1: pre-refresh frames round-trip on the first session.
  - Phase 2: orchestrator recycles (openCount → 2+, wrapper state
    reaches Broadcasting on the new session).
  - Phase 3: post-refresh frames round-trip on a FRESH listener
    subscription against the new session.
  - Wrapper invariant: outward state never surfaced
    Reconnecting / Failed during the recycle.

Both speaker interop tests pass against the real relay.

The "listener subscription survives publisher recycle" gap is a
real production concern for >9-min stage time but lives outside
this PR — it would need either listener-side announce-watching or
a coupled refresh-on-publisher-cycle hook in the listener wrapper.

https://claude.ai/code/session_01HXf3zG3F2ev2ASeQju7Y5S
2026-04-28 13:02:29 +00:00
Claude 138ee12a6a fix(nests): align kind-4312 + kind-30312 wire format with nostrnests/EGG-07
After verifying the nostrnests reference (NestsUI-v2 @ main):
- ProfileCard.tsx writes kicks as ['action','kick'] tags with empty content
- useAdminCommands.ts reads action via tags.find(t => t==='action') and
  applies a 60-s relay since plus a processedRef Set to dedup re-deliveries
- p-tag role marker for moderators is 'admin', not 'moderator'

Amethyst was diverging on every one of those, which means our outbound
admin commands were invisible to nostrnests, theirs to us, and any
nostrnests admin (role='admin') failed our isModerator() / canSpeak()
gates entirely — kicks and force-mutes signed by them were silently
dropped.

Changes:

quartz/AdminCommandEvent.kt
  - Emit ['action', '<verb>'] tag with empty content
  - Reader prefers the tag, falls back to content for any in-flight
    Amethyst-built kick from before this commit
  - kick() and forceMute() share a common build() helper

quartz/ParticipantTag.kt
  - ROLE.MODERATOR.code = 'admin' (matches nostrnests + EGG-07)
  - Adds legacyCodes = ['moderator'] so older Amethyst-emitted
    kind-30312 events still parse as MODERATOR
  - effectiveRole() walks both code + legacyCodes

amethyst/AdminCommandsCollector
  - Filter carries since = now - 60 (EGG-07 #7)
  - Defensive per-event freshness re-check for cached events / clock skew
  - mutableSetOf<String>() processed-id dedup for the lifetime of the
    collector, mirroring useAdminCommands.ts's processedRef

EGG-07.md
  - Documents the Amethyst-only ['action','mute'] extension under a new
    'Implemented extensions' section. nostrnests doesn't emit or honour
    it today; cross-client force-mutes only work between Amethyst peers
  - 'warn' stays in the future-actions list — nostrnests has no plans
    for it either

Tests:
  - ParticipantTagTest: new asserts that 'admin' / 'Admin' / 'ADMIN'
    parse as ROLE.MODERATOR; pins the wire string to 'admin' and the
    legacy alias to 'moderator'
  - AdminCommandEventTest: kick/forceMute templates carry ['action', _]
    tag with empty content; legacy content-form still parses; tag wins
    over content when both are present
2026-04-28 12:59:48 +00:00
Crowdin Bot 64d56e2e2c New Crowdin translations by GitHub Action 2026-04-28 12:43:52 +00:00
Vitor Pamplona bc0179aac6 Merge pull request #2622 from vitorpamplona/claude/test-nests-amethyst-interop-WETiY
Add Nests audio-room interop test harness for Amethyst ↔ web
2026-04-28 08:42:15 -04:00
Claude d41a24f945 feat(nests): empty-stage hint, local hush, moderator/force-mute moderation
Three improvements stacked into one commit since they share files:

#3 Empty-stage hint: StageGrid no longer disappears when nobody is on
   stage. The "Stage" label stays and a quiet "Waiting for speakers…"
   line keeps the strip visible so the room doesn't look broken before
   the first speaker arrives.

#5 Local per-speaker hush: AudioPlayer gains a setVolume(Float)
   default-no-op method; AudioTrackPlayer composes mute and volume
   multiplicatively into AudioTrack.setVolume so a hushed stream stays
   silent regardless of mute state. NestViewModel exposes
   locallyHushed: ImmutableSet<String> in NestUiState plus
   setLocalHushed(pubkey, hushed) — applied at attach time so a
   re-subscribe of an already-hushed speaker stays silent. New "Hush
   this speaker" / "Restore this speaker" row in
   ParticipantHostActionsSheet, available to anyone (it affects only
   our own playback, nothing on the wire).

#4 Host moderation gaps: wires Promote-to-Moderator using the existing
   RoomParticipantActions.setRole(ROLE.MODERATOR) builder — UI gap
   only, no protocol change. Adds a new AdminCommandEvent.Action.MUTE
   variant + AdminCommandEvent.forceMute(room, target) builder; the
   sheet emits it on "Force-mute speaker" and the
   AdminCommandsCollector dispatches incoming MUTE actions to a new
   NestViewModel.onForceMuted() that routes through the existing
   setMicMuted(true) path. Honor-based, same trust model as KICK —
   relays don't enforce signer authority, the client checks the
   signer is host or moderator on the active kind-30312.
2026-04-28 12:41:23 +00:00
Claude bed1b328ce fix(nests): self-terminate the audio-level emitter when no one is talking
The previous emitter ran a permanent while(true){delay} loop on the
viewModelScope from connect-time onward. Cheap in production but
catastrophic for any test that calls runTest's advanceUntilIdle()
after vm.connect() — virtual time never reached idle, hanging
NestViewModelTest's existing connection-state cases for ~15 minutes
before the worker timeout killed them.

Now the emitter is started lazily by onAudioLevel() and exits the
loop the first tick after rawAudioLevels empties, so an idle room
schedules nothing. The next decoded frame restarts it. Idempotent,
so the launch path doesn't need to call it any more — removed the
boot from launchConnect().
2026-04-28 12:08:15 +00:00
Claude 04ee2c3106 feat(nests): pulse the speaker ring with live audio level
Adds an end-to-end audio-amplitude pipeline so on-stage speakers'
green ring throbs in time with their voice (closer to Spaces /
Clubhouse than the previous binary "is in speakingNow" indicator).

NestPlayer.play() now takes an onLevel callback and computes the
normalized peak of each decoded 16-bit PCM frame. NestViewModel
exposes audioLevels: StateFlow<Map<String, Float>>; raw 50 Hz
updates from the decode loop are coalesced into a 10 Hz publish
tick (LEVEL_TICK_MS) so the StateFlow doesn't spam recompositions
across a busy stage. The map is cleared on speaker close, on the
speaking-timeout sweep, and on teardown.

In MemberCell the speaking ring's width animates between
AVATAR_RING_WIDTH (3 dp) and MAX_RING_WIDTH (7 dp) via
animateDpAsState, smoothing the tick into a continuous halo.
Muted-publisher and idle states are unchanged.

Adds NestPlayerTest coverage for the new callback (level math +
empty-PCM short-circuit).
2026-04-28 12:03:13 +00:00
Claude 154b3393a5 feat(nests): role + activity badges, sort speakers / hands to top
- Cap mic, hand-raise, and role badges at 28.dp so they no longer
  dominate the new 100.dp avatars.
- Surface HOST and MODERATOR with a small badge anchored top-left
  (purple medal / sky shield), mirroring nostrnests at-a-glance.
- TalkBack: MicStateBadge announces "Speaking now" / "Microphone
  muted" / "Microphone open" instead of going silent.
- StageGrid floats currently-speaking members to the top so the
  listener doesn't scroll to find who they're hearing; AudienceGrid
  floats raised hands so moderators can approve the queue without
  hunting. Both use stable sorts and Modifier.animateItem() for a
  smooth shuffle.
2026-04-28 11:43:48 +00:00
Claude 837bde6579 feat(nests): leave on host-ended room + speaker-reconnect interop test
Two ship-readiness items.

1. Status: CLOSED handler. The NestActivityBody never observed kind-30312
   status flips, so when a host ended the room every other listener and
   speaker stayed connected to the relay until they manually backed out
   or the JWT expired — silent room with stale "you're in" UI. New
   LeaveOnRoomClosed composable in NestRoomLifecycle.kt watches
   event.isLive() and calls onLeave() when the live event flips to
   CLOSED (or hits the 8 h auto-close cutoff in MeetingSpaceEvent.
   checkStatus). Same teardown path as kick — VM.onCleared() releases
   the listener + speaker when the activity finishes.

2. Speaker-reconnect interop test against the real nostrnests stack,
   mirroring NostrNestsReconnectingListenerInteropTest. Two cases:
   - Happy path: wrapper drives a single real session, frames round-trip.
   - Forced JWT refresh (4 s window): orchestrator recycles the
     underlying speaker mid-stream; frames pre- AND post-recycle must
     all land on the same listener-side SubscribeHandle. Validates the
     production 540 s ↔ 600 s JWT-TTL relationship against the real
     moq-rs relay. Gated by -DnestsInterop=true.

https://claude.ai/code/session_01HXf3zG3F2ev2ASeQju7Y5S
2026-04-28 07:58:01 +00:00
Claude 88c158433c test(nests): add manual interop harness against nostrnests.com
Adds cli/tests/nests/nests-interop.sh — a 47-test operator-driven
interop script that walks a tester through every audio-room edge case
between Amethyst Android and the nostrnests.com NestsUI-v2 web client.

Coverage: bidirectional host/listener flows, audio round-trip on
moq-lite Lite-03, hand-raise + role promotion/demotion, mute, kind:7
reactions (including NIP-30 custom emoji + 30 s overlay clear), kind:1311
in-room chat (text + emoji + link + image upload + history backfill),
kind:4312 kick, room edit + close, scheduled rooms (status=planned),
multi-speaker, background audio + PIP, network drop reconnect, 10-min
JWT refresh, custom moq servers (kind:10112), naddr deep links, and
edge cases (long titles, empty rooms, leave-stage, dedupe, race).

The script follows the marmot/marmot-interop.sh pattern: shared
logging + result helpers, color-coded prompts (yellow=Amethyst,
magenta=web, cyan=optional 3rd identity), p/f/s confirms recorded to a
TSV, and a final summary table. Skip / only / keep-state flags are
supported for iteration.

Pure manual harness — amy does not yet ship `amy nests <verb>` so
nothing is automatable from the CLI side.
2026-04-28 07:52:57 +00:00
Claude d37eb10b8c feat(nests): proactive JWT refresh + reconnect for speaker path
Mirror the listener's ReconnectingNestsListener for the publish side.
moq-auth issues 600 s bearer tokens; without proactive refresh, a user
holding the stage past 10 minutes silently drops when the relay tears
down the WebTransport session and stays off the air until they manually
re-tap Talk. The new wrapper recycles the session at 540 s so the relay
never sees an expired token, and re-issues publishing onto each fresh
session with the user's mute intent replayed on the new handle.

VM swap is a one-line change to DefaultNestsSpeakerConnector. The
caller-owned BroadcastHandle is now the wrapper's stable handle that
survives every refresh.

Six unit tests cover happy path, refresh-without-failure-state, mute
replay across recycle, close idempotence, first-attempt-failure
exception propagation, and post-close startBroadcasting guard.

https://claude.ai/code/session_01HXf3zG3F2ev2ASeQju7Y5S
2026-04-28 03:17:57 +00:00
Claude e027e2ebe8 feat(nests): tighten participant cell min so 4 avatars fit per row
Drops STAGE_CELL_MIN and AUDIENCE_CELL_MIN from 112.dp to 96.dp, which
lets a Pixel-class 411.dp phone fit 4 cells per row instead of 3 (and
unlocks 5/6 columns earlier on tablets). The 100.dp avatars overflow
~2.dp into the 6.dp horizontal arrangement on each side, so neighbors
still show a ~2.dp visible gap. Names ellipsize to the cell width as
before.
2026-04-28 03:13:12 +00:00
Claude 666a93f549 feat(nests): bigger avatars + clearer mute/speaking state in participant grid
100dp avatars across stage and audience tabs, names centered under each
cell, and the avatar border now distinguishes actively speaking (green)
from publishing-but-muted (red) at a glance — the existing mic-pill
keeps its semantics underneath.

UsernameDisplay gains an optional textAlign so the grid can opt in to
centered labels without disturbing other call sites.
2026-04-28 02:44:48 +00:00
Vitor Pamplona 2027f6ad37 Merge pull request #2619 from vitorpamplona/claude/fix-input-event-exception-1NKnN
Move NestActivity to activity subdirectory
2026-04-27 22:17:38 -04:00
Vitor Pamplona eab3a541c2 Softer position 2026-04-27 22:17:00 -04:00
Claude 379334156c fix: correct NestActivity package path in AndroidManifest
The activity class lives in `…nests.room.activity.NestActivity` but the
manifest entry was missing the `.activity` segment, so tapping a Nests
feed card crashed with ActivityNotFoundException.
2026-04-28 02:14:03 +00:00
Vitor Pamplona dbeec35257 Merge pull request #2618 from vitorpamplona/claude/fix-fab-positioning-Ec1pl
Fix FAB positioning when AppBottomBar hides on nested navigation
2026-04-27 22:12:42 -04:00
Claude addd1418f6 Reserve nav-bar inset when DisappearingScaffold's bar renders empty
AppBottomBar hides itself on canPop entries by emitting nothing into the
bar slot. DisappearingScaffold previously read the slot's measured
height as 0 and skipped applying navigationBarsPadding (its
`bottomBar == null` branch only fires when the lambda itself is null),
which let the FAB and feed content slide under the system navigation
bar. Fall back to the system-nav-bar inset when the slot measures zero
so the FAB lines up with the bar-visible position and content stays
clear of the gesture/3-button nav.

https://claude.ai/code/session_01QSk7CnjNtbcgS3XBD5WEZy
2026-04-28 02:08:20 +00:00
Vitor Pamplona 7d2741bd68 Merge branch 'main' of https://github.com/vitorpamplona/amethyst 2026-04-27 22:07:46 -04:00
Vitor Pamplona c59e60117d Merge pull request #2617 from vitorpamplona/claude/check-live-audio-status-ZEp60
Add presence-based liveness detection for meeting rooms
2026-04-27 22:06:50 -04:00
Claude f3471be70f Merge remote-tracking branch 'origin/main' into claude/check-live-audio-status-ZEp60
# Conflicts:
#	amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/nests/NestsFeedLoaded.kt
2026-04-28 02:03:57 +00:00
Claude a6d7b4f4a3 refactor: reuse NestRoomFilterAssembler for thumb liveness probe
Drop the dedicated NestThumbStatusFilterAssembler and have the
thumb's liveness gate piggyback on NestRoomFilterAssembler, which
already subscribes to kinds=[1311, 10312, 7] + admin (4312) per
room a-tag. observeRoomLatestPresence is now a private composable
in NestsFeedLoaded.kt that opens NestRoomFilterAssemblerSubscription
and reads the latest cached MeetingRoomPresenceEvent createdAt off
the room's LiveActivitiesChannel.

Trade-off: the thumb pays for the room's full chat + reaction
history, not just presence. Bounded by LazyColumn — only currently-
composed thumbs hold subscriptions — and the cache it warms is
exactly what NestActivity needs when the user opens the room. If
wire load becomes a concern, swap to a limit:1 lite mode on the
existing assembler.

Net: -159 / +51 lines, one less filter assembler in the coordinator.
2026-04-28 02:00:48 +00:00
Claude 98dcaf4155 Apply FabBottomBarPadded to remaining FAB screens
Extends the canPop FAB-position fix to every screen that hosts a
floating action button: Pictures, Videos, Articles, Longs, Shorts,
Polls, Products, Communities, Nests, Badges, Discover, WebBookmarks,
Messages, Home, GeoHash, Hashtag, Relay, Community, OldBookmarks,
EmojiPack, ChessLobby, MarmotGroups, the bookmark management screens
and the migration FAB. Adds a small FabBottomBarPadded helper so call
sites that use named FAB composables stay tidy.

https://claude.ai/code/session_01QSk7CnjNtbcgS3XBD5WEZy
2026-04-28 01:58:45 +00:00
Claude b3da1e87b9 feat: gate nests thumb LiveFlag on kind-10312 presence freshness
The kind-30312 status tag goes stale silently when a host crashes
without publishing a CLOSED replacement, so checkStatus()'s 8-hour
fallback was the only liveness signal — abandoned rooms still
showed LiveFlag for up to 8h after the audio stopped.

Adds a per-thumb relay probe: kinds=[10312], #a=[roomATag], limit=1.
The relay returns the single most-recent presence event among all
speakers in the room, then keeps streaming so fresh heartbeats land
in real time. Speakers re-emit kind-10312 every ~60 s while
publishing, so a presence newer than 180 s means at least one
speaker is still in the room; older flips the badge to EndedFlag
even though the 30312 event still says OPEN.

UX: optimistic on first paint (LiveFlag while we have no presence
data), self-corrects within one round-trip once the relay replies.

Files:
- NestThumbStatusFilterAssembler.kt: per-room limit:1 REQ +
  observeNestRoomLatestPresence composable that scans the
  LiveActivitiesChannel's notes for max(createdAt) of cached
  presence events.
- RelaySubscriptionsCoordinator.kt: register nestThumbStatus.
- NestsFeedLoaded.kt: thread card.id into a private
  RenderLiveOrEndedFromPresence helper that gates the OPEN badge
  on freshness.
2026-04-28 01:53:37 +00:00
Vitor Pamplona b70a5ee31c Fixes weird transparency in the Chat Screen 2026-04-27 21:49:22 -04:00
Vitor Pamplona 833dcf1048 Fixes size of Send icons 2026-04-27 21:49:07 -04:00
Vitor Pamplona b0a2234ae1 Merge pull request #2615 from vitorpamplona/claude/refactor-room-package-YQ22H
refactor(nests): split nests/room flat package into named subpackages
2026-04-27 21:43:21 -04:00
Claude ea4da13110 Merge remote-tracking branch 'origin/main' into claude/refactor-room-package-YQ22H
# Conflicts:
#	amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/nests/create/CreateNestViewModel.kt
2026-04-28 01:42:12 +00:00
Vitor Pamplona 15ae0b9032 Merge pull request #2616 from vitorpamplona/claude/nest-image-upload-We3jR
Add image upload functionality to CreateNestSheet
2026-04-27 21:41:17 -04:00
Claude 618ebabd30 Keep FAB position stable when AppBottomBar is hidden
AppBottomBar returns nothing on canPop entries (drawer pushes, in-app
navigations), so the floating action button drops by the bar's height
when navigating into a screen and rises again on a tab root. Reserve the
50dp gap at the FAB site via a fabBottomBarPadding(nav) modifier so the
button stays at a consistent vertical position regardless of the bar's
visibility on EmojiPacks, BookmarkGroups, and InterestSets.

https://claude.ai/code/session_01QSk7CnjNtbcgS3XBD5WEZy
2026-04-28 01:39:30 +00:00
Claude 731d01bf7e Merge remote-tracking branch 'origin/main' into claude/refactor-room-package-YQ22H 2026-04-28 01:37:25 +00:00
Claude d92db861a9 fix: use MeetingSpaceEvent.checkStatus() for nests feed thumb
NestsFeedLoaded.RenderLiveSpacesThumb gates LiveFlag/EndedFlag on
the kind-30312 status tag verbatim, so a host that crashed without
publishing a CLOSED replacement leaves the room flagged "live"
indefinitely. checkStatus() applies the same 8-hour age-out the
event's own helper uses, so abandoned OPEN rooms surface as
EndedFlag instead.

Also drop a stray println debug ("AABBCC …") in the same map block.
2026-04-28 01:36:58 +00:00
Claude cefda4b1f2 Allow uploading the Nest cover image from the gallery
Mirrors the Profile Edit banner upload: a SelectSingleFromGallery
leadingIcon on the Cover Image field picks media, strips metadata,
compresses, and uploads to the user's configured Blossom (or NIP-96)
server. The returned URL fills the field so the existing publish path
tags it on the kind-30312 MeetingSpaceEvent.
2026-04-28 01:36:29 +00:00
Claude c7fba2821a fix(nests): align relocated test packages with their subpackage paths
The previous refactor renamed EditNestViewModelTest and
RoomParticipantActionsTest into edit/ and participants/ subdirs but
the package declarations stayed at the old flat path, so the tests
no longer compiled against the relocated production code.
2026-04-28 01:30:49 +00:00
Claude a0fa2de87d refactor(nests): split nests/room flat package into named subpackages
The nests/room/ package had grown to 23+ files in one flat directory,
making it hard to find related composables and lifecycle helpers. Each
file is now grouped by responsibility:

  activity/     Android Activity entry + AccountViewModel bridge
  lobby/        Pre-join lobby card (NestJoinCard, JoinNestButton)
  lifecycle/    Side-effect composables wiring VM <-> system / cache
  screen/       Top-level layouts (FullScreen, PipScreen, ActionBar)
  stage/        Speaker / audience grids and overlays
  participants/ Per-participant host actions + role mutations
  reactions/    Reaction picker
  chat/         Chat panel and composer (also absorbs the old send/)
  edit/         Edit-room sheet + ViewModel
  theme/        Themed scope + URL font loader

Also drops the unused StagePeopleRow composable from NestCommon and
inlines its remaining one-liner connectingLabel helper into
NestActionBar (the only caller), removing NestCommon entirely.

External imports in 7 sibling files were updated to point at the new
subpackage paths.
2026-04-28 01:28:47 +00:00
Vitor Pamplona 68e405c684 Merge branch 'main' of https://github.com/vitorpamplona/amethyst 2026-04-27 21:06:24 -04:00
Vitor Pamplona 9fcf7bc1e5 Merge pull request #2614 from vitorpamplona/claude/fix-nest-activity-layout-zziPP
Refactor NestFullScreen layout: tabs, action bar, and stage grid
2026-04-27 21:04:35 -04:00
Claude 4f391ed74f Merge branch 'main' into claude/fix-nest-activity-layout-zziPP
Resolves NestFullScreen.kt by keeping the redesigned layout (header
strip, stage grid, tabs, sticky NestActionBar) while dropping the
NestThemedScope wrapper that main removed in ff00dcf3 ("screens get
too dark"). The RoomTheme import goes with it; the stale doc-comment
reference to NestThemedScope is updated accordingly.
2026-04-28 01:01:08 +00:00