Commit Graph

12410 Commits

Author SHA1 Message Date
Claude 461147af81 docs(nests): update plan doc for round-2 listener survival fixes
Captures the group-rotation, orchestrator-break-on-Closed removal,
ensureAnnounceWatchStarted, handleInboundBidi refactor, and
removeInboundSubscription changes that landed in d8ab4fd9. All
three reconnecting-listener interop scenarios now pass.
2026-04-28 17:23:10 +00:00
Claude d8ab4fd99b fix(nests): rotate moq-lite groups + reconnect after publisher cycle
To make a SubscribeHandle survive a publisher session swap on the same
relay, three things had to change together:

1. Broadcaster emits one Opus frame per moq-lite group
   (`publisher.send` + `publisher.endGroup`). moq-lite's "from-latest"
   subscribe semantics deliver a new subscriber the NEXT group's
   frames; without per-frame rotation a subscriber that attaches
   mid-broadcast waits forever for the (single, never-ending) group
   to end.
2. MoqLiteSession opens its announce-watch bidi synchronously before
   the first subscribe, dispatches subscribe + announce frames over a
   single long-running collector (varint type code hoisted outside
   `collect`), and FINs the publisher's currentGroup when an inbound
   subscribe bidi closes — so the next send opens a fresh group keyed
   off the live subscriber instead of the recycled one.
3. ReconnectingNestsListener no longer breaks on terminal=Closed in
   the orchestrator; the user-driven stop path goes through
   `orchestrator.cancel()`, so any other Closed (peer-driven
   transport close, half-broken session, publisher recycle) is a
   reconnect trigger.

Round-trip interop test updated to assert `groupId == idx` (one group
per frame) rather than `groupId == 0`. All three reconnecting-listener
interop scenarios now pass against the real moq-rs relay:
happy-path, session-swap, and listener-survives-publisher-recycle.
2026-04-28 17:06:09 +00:00
Claude 5f71dc7c76 test(nests): fix nextSubscribeBidi helper to terminate after match
The takeWhile/collect pattern only re-checks its predicate when the
NEXT upstream value emits, so once nextSubscribeBidi found its
target Subscribe bidi, the helper sat blocked waiting for a
follow-up bidi that may never come — turning groups_are_demuxed_by_
subscribeId into a 5-minute hang on tests that open exactly two
subscribes (the announce-watch bidi happened to land between, but
relying on it to nudge the flow forward is fragile).

Switched to transformWhile { … emit(…); false }.firstOrNull() so
the upstream collection terminates synchronously after the first
match. Warm-daemon test time drops from multi-minute timeout back
to the expected ~10 ms range.

https://claude.ai/code/session_01HXf3zG3F2ev2ASeQju7Y5S
2026-04-28 16:22:01 +00:00
Claude cd9279d23b test(nests): skip housekeeping bidi in groups_are_demuxed_by_subscribeId
The session-layer fix in 851045c6 lazy-launches a single shared
announce-watch bidi on first subscribe (publisher-disconnect
detection). The unit test groups_are_demuxed_by_subscribeId
assumed each session.subscribe() opened exactly one peer-side bidi
and grabbed them by raw `peerOpenedBidiStreams().first()`, which
races with the announce-watch bidi.

New helper nextSubscribeBidi(serverSide) iterates accepted bidis,
peeks the control-byte varint, and skips any that aren't Subscribe.
The race window (announce-watch bidi between subscribe #1 and
subscribe #2) is now handled gracefully — the test reliably
finds both subscribe bidis regardless of the announce-watch's
launch ordering.

Also extends the listener-survives-publisher-recycle plan doc
with the full diagnosis of why
reconnecting_wrapper_keeps_handle_alive_across_session_swap
remains a separate, narrower failure (publisher single-group
architecture: NestMoqLiteBroadcaster never rotates groups, so
mid-stream listener resubscribes have nothing to attach to).

https://claude.ai/code/session_01HXf3zG3F2ev2ASeQju7Y5S
2026-04-28 16:03:11 +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 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 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 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 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
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
Vitor Pamplona 6f38c5ec82 Merge pull request #2613 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2026-04-27 20:55:25 -04:00
Vitor Pamplona 637174ef12 Add relays to the broadcast for Nests 2026-04-27 20:54:58 -04:00
Vitor Pamplona 6c284a31bf Removes the rest of the theming 2026-04-27 20:50:13 -04:00
Crowdin Bot d7c6e9b169 New Crowdin translations by GitHub Action 2026-04-28 00:40:23 +00:00
Vitor Pamplona ff00dcf3c6 Removes the implementation of custom designs because our screens get too dark 2026-04-27 20:38:18 -04:00
Claude 9381a7731d feat(nests): redesign room layout with stage grid, tabs, sticky action bar
Replaces the single scrolling Column (title → speakers row → audience
row → host queue → connection chip → talk row → buttons → chat) with
a Scaffold-bounded layout that scales to large rooms:

  - Header strip with LIVE chip + listener count (uses the existing
    nest_listener_count plural for the first time) and a 1-line
    ellipsised summary. Topbar stays slim.
  - StageGrid: vertical adaptive LazyVerticalGrid capped at 220dp,
    so a 30-speaker room scrolls inside the strip without pushing
    the chat below the fold.
  - Tab bar (Chat / Audience · N / Hands · N) with badge counts.
    Hands tab is host-only and only shown while the queue is non-empty.
  - AudienceGrid: vertical adaptive LazyVerticalGrid that fills the
    selected tab — handles 1,000+ listeners without performance issues
    (the old single-row LazyHorizontalGrid only let you see ~5 at a time).
  - NestActionBar: sticky bottom bar consolidating the old ConnectionRow,
    TalkRow, and hand/react/leave row. Layout adapts to connection /
    broadcast / on-stage state; failure messages get a thin status
    strip above the bar instead of stealing button slots.
  - Hand-raise control hides when the user is on stage (the action it
    represents — "I want to speak" — no longer applies).

The PIP screen and chat panel internals are untouched.
2026-04-28 00:37:32 +00:00
Vitor Pamplona 9d0c224c9e New Private FLAG for Nests 2026-04-27 20:36:45 -04:00
Vitor Pamplona 89176048f1 Merge branch 'main' of https://github.com/vitorpamplona/amethyst 2026-04-27 20:36:26 -04:00
Vitor Pamplona 078c5f04a8 Solving the issue of a BackArrow on the Home Screen after a back press 2026-04-27 20:35:40 -04:00
Vitor Pamplona eb305a61f2 Merge pull request #2610 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2026-04-27 19:26:35 -04:00
Vitor Pamplona 96f976449e Merge pull request #2611 from vitorpamplona/claude/add-topbar-profile-screen-yw8IQ
Extract profile top bar into separate composable component
2026-04-27 19:26:25 -04:00
Vitor Pamplona a9f428526b Merge branch 'main' of https://github.com/vitorpamplona/amethyst 2026-04-27 19:25:21 -04:00
Vitor Pamplona a50a360964 Correctly downloads Nest events 2026-04-27 19:23:52 -04:00
Claude 152900688d Move profile back/more buttons into Scaffold topBar
The back button and the more-options menu used to float as
absolute-positioned overlays inside ProfileHeader. They are now
hosted in a transparent ShorterTopAppBar wired into the Scaffold's
topBar slot, so the banner can still render edge-to-edge as the
background by setting contentWindowInsets to zero and only applying
the bottom padding from Scaffold to the body.
2026-04-27 23:09:57 +00:00
Crowdin Bot afde1c8499 New Crowdin translations by GitHub Action 2026-04-27 22:58:19 +00:00
Vitor Pamplona b786b056bb Merge pull request #2609 from vitorpamplona/claude/add-chat-options-nest-PIyCp
Add full-featured composer to nest room chat
2026-04-27 18:56:50 -04:00