Commit Graph

12373 Commits

Author SHA1 Message Date
Vitor Pamplona 6f38c5ec82 Merge pull request #2613 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2026-04-27 20:55:25 -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
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
Claude 995f8384df feat(nests): wire nest chat composer to NestNewMessageViewModel
Completes the copy approach started in 57ebded6:

  - NestEditFieldRow.kt: copy of EditFieldRow, drops the BackHandler
    (the activity owns back; draft auto-save runs on every keystroke
    so there is nothing to flush) and points every callback at
    NestNewMessageViewModel.
  - NestFileUploadDialog.kt: copy of ChannelFileUploadDialog, header
    shows the room host's avatar + room name from the loaded
    MeetingSpaceEvent.
  - NestChatPanel.kt: replaces the slim inline composer with
    NestEditFieldRow + NestNewMessageViewModel. Chat message list is
    still rendered from NestViewModel.chat — the new VM is scoped to
    *editing and sending* messages only, per the brief.
  - NestNewMessageViewModel.sendPostSync: switched from
    signAndSendPrivately(template, emptySet()) to
    signAndComputeBroadcast(template) so messages actually reach the
    user's default relays (matches the original slim composer's
    behaviour).
2026-04-27 22:37:18 +00:00
Claude 57ebded68b wip(nests): start copying ChannelNewMessageViewModel into nest package
Pivot from sharing ChannelNewMessageViewModel (previous commit) to a
literal per-nest copy as requested.

State of this commit (incomplete — DO NOT MERGE):
  - EditFieldRow.kt: reverted to original (no interceptBackPress flag)
  - NestNewMessageViewModel.kt: new file, copy of ChannelNewMessageViewModel
    narrowed to MeetingSpaceEvent + LiveActivitiesChatMessageEvent only

Still TODO before this branch is usable:
  - Copy EditFieldRow into nests/room/send/NestEditFieldRow.kt
  - Copy ChannelFileUploadDialog into nests/room/send/NestFileUploadDialog.kt
  - Rewire NestChatPanel to use these copies (it currently still calls
    ChannelNewMessageViewModel + EditFieldRow from the previous commit's
    approach, which won't compile against the reverted EditFieldRow
    signature once NestChatPanel is also reverted)

User asked to stop mid-work — committing as WIP so the tree is clean.
2026-04-27 22:31:46 +00:00
Claude 46cfab4fa1 feat(nests): use shared EditFieldRow for nest chat composer
Wire the in-room chat composer to ChannelNewMessageViewModel +
EditFieldRow so nest chat picks up @-mention picker, file/image/
video upload, reply preview, NIP-37 draft auto-save, emoji
suggestions, content warnings, expiration, geohash, and zap-
split forwarding — the same affordances every other public chat
surface has.

The composer is backed by a LiveActivitiesChannel keyed by the
meeting space's address. ChannelNewMessageViewModel.createTemplate()
already handles the LiveActivitiesChannel branch: when channel.info
is null (which it always is for kind-30312, since info is typed to
the kind-30311 LiveActivitiesEvent), it falls through to
LiveActivitiesChatMessageEvent.message(post, channel.toATag()) —
the exact event shape the previous slim composer was emitting.

EditFieldRow gains an interceptBackPress flag so nest can opt out
of the channel-screen back-handler that would clear the field and
call nav.popBack() (a no-op on BouncingIntentNav).
2026-04-27 22:09:45 +00:00
Vitor Pamplona 3c3e327bcb Fixes test cases 2026-04-27 18:01:44 -04:00
Vitor Pamplona 40fa069a98 Merge branch 'main' of https://github.com/vitorpamplona/amethyst 2026-04-27 17:55:15 -04:00
Vitor Pamplona 1e86e15291 Adjustments to the loading and updating of the nest event in a PiPActivity 2026-04-27 17:54:16 -04:00
Vitor Pamplona 81b82198e2 Corrects the name of the room in Nests feed 2026-04-27 17:27:11 -04:00
Vitor Pamplona f37f1907a4 Merge pull request #2608 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2026-04-27 16:59:06 -04:00
Crowdin Bot 950f226375 New Crowdin translations by GitHub Action 2026-04-27 20:53:31 +00:00
Vitor Pamplona 414707c3cb Merge pull request #2607 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2026-04-27 16:51:52 -04:00
Vitor Pamplona f403ae9615 Merge pull request #2585 from vitorpamplona/claude/audio-rooms-android-ui-PIpFN
Add NIP-53 audio room speaker/broadcaster support
2026-04-27 16:51:31 -04:00
Claude 6da4483fa3 Merge remote-tracking branch 'origin/main' into claude/audio-rooms-android-ui-PIpFN
# Conflicts:
#	amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/audiorooms/AudioRoomsScreen.kt
2026-04-27 20:45:04 +00:00
Crowdin Bot f1fc9d43fb New Crowdin translations by GitHub Action 2026-04-27 20:43:16 +00:00
Vitor Pamplona a0f45b49e8 Merge pull request #2606 from vitorpamplona/claude/fix-nav-back-arrow-99NGn
Adds a BottomNavBar for screens that didn't have before
2026-04-27 16:41:37 -04:00
Claude 8c5804063e feat(nests): wrap room screen in Scaffold; title + overflow → TopAppBar
The room name and the MoreVert overflow lived inline at the top of
the metadata Column, so they scrolled with the rest. Hoist them into
a Material 3 TopAppBar inside a Scaffold, matching every other major
screen in the app.

NestTopAppBar — new private composable:
  - Title is `event.room()`, single-line ellipsis.
  - Actions: Box wrapping the MoreVert IconButton + DropdownMenu
    (Share for everyone, Edit gated on `isHost`).
  - containerColor = Transparent so the themed background painted by
    NestThemedScope's Surface (and any optional `bg` image) shows
    through cleanly without a double-paint.

NestFullScreen body now:
  Scaffold(
    containerColor = Color.Transparent,
    topBar = { NestTopAppBar(...) },
  ) { padding ->
    Column(fillMaxSize.padding(padding)) {
      Column(weight(1, false), verticalScroll, hpad+top pad) {
        // summary, listener count, participants grid, host actions
        // sheet, hand-raise queue, connection row, talk row, action
        // row, reaction picker, host-leave confirm dialog
      }
      NestChatPanel(weight(1, true), hpad+bottom pad)
    }
  }

Side effects:
  - Drops the manual windowInsetsPadding(safeDrawing) — Scaffold's
    contentWindowInsets handles that and the TopAppBar's own
    windowInsets handles the top edge.
  - The EditNestSheet conditional moves to NestThemedScope's body
    (after the Scaffold). It's a ModalBottomSheet, so position in
    the tree doesn't change layout, but adjacency to the Scaffold
    keeps the sheet/dialog boundary obvious.

https://claude.ai/code/session_01RDpuki4t8StSg1CZcXnV5b
2026-04-27 20:36:28 +00:00
Claude baaee94555 feat(nav): add bottom bar to remaining tab-eligible screens
The bottom-nav settings let users place any of these routes in the bar,
but the screens themselves never rendered AppBottomBar — so even when
configured as a tab, the bar was missing on Profile, Wallet, Lists,
BookmarkGroups, WebBookmarks, Drafts, InterestSets, EmojiPacks,
BrowseEmojiSets, and AllSettings.

AppBottomBar self-hides via nav.canPop(), so adding the bar is safe on
every entry: it appears only when the screen is the tab root, hidden
otherwise (drawer / deep link).

Same-tab tap scrolls the screen's main list to the top, mirroring the
existing tab behavior. State is hoisted as needed:
- WebBookmarks, Drafts, BrowseEmojiSets: feedState.sendToTop()
- Lists, BookmarkGroups, EmojiPacks: hoisted LazyListState/LazyGridState
  through the feed-view helper, animateScrollTo(0)
- Wallet, InterestSets: hoisted LazyListState locally
- AllSettings: hoisted ScrollState (verticalScroll column)
- Profile: wrapped in Scaffold, hoisted ScrollState from RenderSurface

https://claude.ai/code/session_01PrirRcL7g8iX7vTqqLTkBS
2026-04-27 20:35:56 +00:00
Claude 84712f48f1 fix(nests): NestChatPanel takes weight(1) so chat fills the screen
Previously the room screen wrapped EVERYTHING — title, summary,
participants, talk row, action row, AND chat — in a single
verticalScroll Column. The chat panel sat at the bottom with a fixed
NEST_CHAT_PANEL_HEIGHT (420dp), which meant on tall phones the chat
left blank space below it and on small phones the chat was cramped.

Restructure NestFullScreen's body:
  - Outer Column.fillMaxSize, no scroll, owns safeDrawing inset.
  - Top metadata: Column with weight(1f, fill=false), internal
    verticalScroll, horizontal+top padding. Caps at half the screen
    so an over-tall participants list scrolls internally instead of
    pushing the chat off-screen.
  - NestChatPanel: Column-scoped weight(1f, fill=true), horizontal
    padding + bottom inset. Always takes its full allocation —
    chat dominates the screen, fixed-height fallback gone.

NestChatPanel is now a `ColumnScope.NestChatPanel` extension so its
modifier can use `weight()` from the caller. Inside the panel, the
message list Box also uses weight(1f, fill=true) of the panel's own
Column, so the LazyColumn fills everything except the composer.

NEST_CHAT_PANEL_HEIGHT constant removed — it was the source of the
fixed-height behavior the user explicitly didn't want.

https://claude.ai/code/session_01RDpuki4t8StSg1CZcXnV5b
2026-04-27 20:31:08 +00:00
Claude 759fd67ef5 fix(nav): hide bottom bar on screens reached via non-tab-root entries
Bottom-nav screens (Notifications, Discover, etc.) used to render the
bottom bar unconditionally. When reached as a non-tab-root entry —
drawer, deep link, in-app navigation that pushed a fresh copy with
different args — the bar should disappear, matching the back-arrow and
slide-animation rules.

AppBottomBar now takes an INav and returns early when nav.canPop() is
true. The check covers two valid cases for showing the bar:
  - Tab-root entry (carries BOTTOM_NAV_ROOT_KEY, canPop is false).
  - Graph start destination Home (no previous entry, canPop is false).

All 19 callers updated to pass nav; the existing click callback param
is renamed onClick to free up the nav name.

https://claude.ai/code/session_01PrirRcL7g8iX7vTqqLTkBS
2026-04-27 20:00:28 +00:00
Claude 7f48b18319 fix(nests): wrap NestThemedScope in Surface so LocalContentColor flips
NestThemedScope painted `themed.background` on a plain Box. Compose's
`LocalContentColor` was never updated, so it stayed at its
CompositionLocal default — `Color.Black` — for every Text inside.
On the user's dark theme that meant black-on-black for the room
title and the chat panel: unreadable.

AmethystTheme installs `MaterialTheme(colorScheme = ...)` but does
NOT wrap content in a Surface; screens that use Scaffold inherit
LocalContentColor via Scaffold's internal Surface. NestFullScreen
goes Column → verticalScroll, no Scaffold, no Surface, so the
black-default fell through.

Replace the bare `Box.background(themed.background)` with a Material
Surface that paints `color = themed.background` AND provides
`contentColor = themed.onBackground` to LocalContentColor for the
inner subtree. The `bg` image still overlays the Surface inside the
Box, exactly as before.

This was the actual cause of the user's "title impossible to read /
chat super dark" report — the event in question shipped no `c` tags
(only the unparseable `["color", "gradient-7"]`), so RoomTheme was
already Empty. The previous "all-or-nothing palette" gate (commit
15169de7) is still right for half-applied palettes; this Surface
fix is what makes empty / un-themed rooms read correctly on dark.

https://claude.ai/code/session_01RDpuki4t8StSg1CZcXnV5b
2026-04-27 19:54:45 +00:00
Claude 15169de7e5 fix(nests): drop themed colors when palette is incomplete
EGG-10 lets each `["c", hex, role]` tag stand alone, but a half-
applied palette (themed background + platform text, or themed text
+ platform background) collides with whichever system theme — light
or dark — the user is on. A room that ships ONLY `background=#FFE4B5`
ends up with platform-default white text on dark theme; unreadable.

Gate all three color overrides on having a complete bg + text pair
inside `RoomTheme.from(event)`. When either is missing the whole
palette drops to null and renderers fall through to the platform
theme. Background image (`bg`) and font tags still flow through —
they don't break contrast on their own.

Both consumers — NestThemedScope (room screen) and NestJoinCard
(lobby) — pick up the change without code edits because they read
through the same `RoomTheme` projection.

The nostrnests-style `["color", "gradient-7"]` tag was never parsed
(our `c`-tag parser ignores it), so events shipping only that don't
override anything either way; this fix targets the genuine half-
applied case (rooms that emit one EGG-10 c-tag without the matching
companion).

https://claude.ai/code/session_01RDpuki4t8StSg1CZcXnV5b
2026-04-27 19:46:18 +00:00
Claude 1a6b9a4cee Merge remote-tracking branch 'origin/main' into claude/audio-rooms-android-ui-PIpFN 2026-04-27 19:30:18 +00:00
Vitor Pamplona cf706abb99 Merge pull request #2605 from vitorpamplona/claude/fix-nav-back-arrow-99NGn
Claude/fix nav back arrow 99 n gn
2026-04-27 15:27:32 -04:00
Claude 41aa908fbe refactor(nav): collapse SKIP_SLIDE_ANIMATION_KEY into BOTTOM_NAV_ROOT_KEY
Both flags were stamped at the same call site (navBottomBar) and meant
the same thing — "this entry is a bottom-nav tab root". Collapse them
into a single key and use isBottomNavRoot() in composableFromEnd's
transition lambdas.

https://claude.ai/code/session_01PrirRcL7g8iX7vTqqLTkBS
2026-04-27 19:19:10 +00:00
Claude 32157ff82f fix(nav): keep Home below tab roots, hide back arrow via per-entry flag
Previous attempt cleared Home from the back stack on every bottom-nav
tap, which broke back-swipe: the user expects swipe-back from any tab
to return to Home, and swipe-back from Home to leave the app.

Restore that contract while still hiding the back arrow on tab roots:
- navBottomBar pops up to (but not including) Route.Home, so sibling
  tabs are cleared while Home stays at the bottom of the stack.
- The new entry is stamped with BOTTOM_NAV_ROOT_KEY on its
  savedStateHandle.
- Nav.canPop returns false when the current entry carries that flag,
  even though previousBackStackEntry (Home) is non-null.

Home itself is the graph's start destination and has no previous entry,
so canPop returns false there without needing the flag.

https://claude.ai/code/session_01PrirRcL7g8iX7vTqqLTkBS
2026-04-27 19:16:37 +00:00
Claude 8d53c9025d feat(meetingrooms): show parent MeetingSpace link on kind:30313
A NIP-53 kind-30313 [MeetingRoomEvent] references its parent kind-30312
[MeetingSpaceEvent] via the standard `["a", "30312:<host>:<d>"]` tag.
Without surfacing it, a meeting card in a feed lost the context of
which Nest the meeting belonged to — users couldn't tell if "Office
Hours Q3" lived inside their company nest or someone else's.

Render a small "In <Space Name>" label above the meeting's title
inside [RenderMeetingRoomEventInner]:

  - Resolves the parent address via `MeetingRoomEvent.interactiveRoom()`
    → `MeetingSpaceTag.address`. Bails when the address points at
    something other than kind:30312 (no broken links).
  - Loads the parent through the existing `LoadAddressableNote`
    helper. When the kind:30312 hasn't propagated to LocalCache yet,
    the row falls back to a generic "In a Nest" label and tapping
    routes through the standard `routeFor` thread path which
    auto-fetches.
  - Tap navigates to the parent space's thread view via `routeFor`
    + `nav.nav` — the same path Discovery / NoteCompose use for
    quote-into-thread navigation.

Two new strings:
  - meeting_room_in_space ("In %1$s")
  - meeting_room_in_space_unknown ("a Nest" — fallback when the
    parent space isn't yet resolvable)

https://claude.ai/code/session_01RDpuki4t8StSg1CZcXnV5b
2026-04-27 19:10:12 +00:00
Claude 0ceeafe90c revert(meetingrooms): drop the kind:30313 drawer split
User changed direction — the Nests / Meeting Rooms separation was
the wrong call. Kind 30313 stays out of the Nests list, but doesn't
get its own drawer entry either; it surfaces through the standard
NoteCompose / thread paths instead.

Reverted:
  - Deleted: meetingrooms/MeetingRoomsScreen, MeetingRoomsTopBar,
    MeetingRoomsFeedLoaded, dal/MeetingRoomsFeedFilter.
  - Removed Route.MeetingRooms, NavBarItem.MEETING_ROOMS, the
    catalog entry, the drawer entry.
  - Removed meetingRoomsFeed from AccountFeedContentStates and
    ScrollStateKeys.MEETING_ROOMS_SCREEN.
  - Removed `meeting_rooms` string.
  - Updated AppNavigation imports.

Kept:
  - NestsFeedFilter narrowed to kind:30312 only (per user: "only
    show kind 30312 in the items of the NestScreen"). The dual-kind
    branches stay gone.
  - Doc comment updated to note that kind:30313 renders on the
    standard NoteCompose / thread paths outside the Nests drawer.

https://claude.ai/code/session_01RDpuki4t8StSg1CZcXnV5b
2026-04-27 19:07:44 +00:00
Claude befa13d797 Merge remote-tracking branch 'origin/main' into claude/audio-rooms-android-ui-PIpFN 2026-04-27 19:00:48 +00:00
Claude 3e19651d22 feat(nests): show like + zap row on Nest / MeetingRoom cards
Discovery's RenderLiveActivityThumb cards already render a creator
+ name + like + zap row at the bottom — but only when the channel
holds a kind:30311 LiveActivitiesEvent. Nests (kind:30312) and
Meeting Rooms (kind:30313) populate channels with the same address
shape but `LiveActivitiesChannel.info` is null for them, so the
reactions row was hidden.

ShortLiveActivityChannelHeader:
  - Add an `addressableNote: Note?` parameter to the inner overload.
    The outer (LiveActivitiesChannel) overload fills it from
    `LocalCache.getAddressableNoteIfExists(channel.address)`, which
    works for any kind that registers an addressable note.
  - Move the like/zap row OUT of the `liveActivitiesEvent?.let { }`
    gate so it renders for any addressable note — kind:30311,
    30312, 30313 all share it now.
  - Keep the LIVE / OFFLINE flag inside the LiveActivitiesEvent gate
    (it pings the streaming URL — stream-specific). Extracted into
    a small `LiveChannelLiveFlag` helper so the reaction row can
    render independently.

MeetingRoomsFeedLoaded:
  - Swap from RenderMeetingRoomEvent to RenderLiveActivityThumb so
    the Meeting Rooms list visually matches the Nests list and
    Discovery — same cover-image card with status flag, participant
    gallery, and bottom header (creator + name + reactions).

Other call sites (LiveActivityTopBar, LiveActivitiesChannelHeader)
use the outer `baseChannel` overload and pick up the new behavior
automatically.

https://claude.ai/code/session_01RDpuki4t8StSg1CZcXnV5b
2026-04-27 18:51:33 +00:00
Vitor Pamplona 09f653950e Merge pull request #2604 from vitorpamplona/claude/fix-nav-back-arrow-99NGn
Fix back stack management in bottom navigation
2026-04-27 14:49:03 -04:00
Claude 0e4379c5bf fix(nav): clear back stack on bottom-nav taps so no back arrow shows
popUpTo(route) { inclusive = true } only pops if `route` is already in
the stack. From Home, tapping any other bottom-nav tab left Home in the
back stack, so canPop() returned true and the back arrow appeared on a
root tab.

Pop up to the graph's start destination instead (also inclusive). This
clears Home — and any drawer/deep-push entries above it — before
navigating to the new bottom-nav root, so each bottom-nav tap leaves
exactly one entry in the stack.

https://claude.ai/code/session_01PrirRcL7g8iX7vTqqLTkBS
2026-04-27 18:39:23 +00:00
Claude 4782b7b6ac feat(meetingrooms): split kind:30313 into its own drawer screen
Nests (kind:30312) and Meetings (kind:30313) were sharing one drawer
entry / one feed; the only thing they have in common is being NIP-53
events. They have different lifecycles (audio rooms vs scheduled
meetings) and different status enums (open/private/closed/planned vs
live/planned/ended). Mixing them under the Nests label was confusing.

Split:

- MeetingRoomsFeedFilter (new) — narrows to MeetingRoomEvent (30313),
  with title-must-be-non-blank gate (mirrors the EGG-01-style minimum-
  fields rule the Nests feed applies). Sort by status (LIVE > PLANNED
  > ENDED), then participants among follows, then `starts` / created.
- NestsFeedFilter (existing) — narrowed to MeetingSpaceEvent (30312)
  only. Drops the dual-kind branches in `innerApplyFilter` and the
  parallel sub-room enum in `convertStatusToOrder`. Same EGG-01 gate
  (room/status/service/endpoint).

UI:

- New MeetingRoomsScreen / MeetingRoomsFeedLoaded / MeetingRoomsTopBar.
  Read-only — the app doesn't compose kind-30313 events locally; tap
  routes a card to the standard thread view via `routeFor`.
- Reuses the existing in-feed renderer
  (`note.types.RenderMeetingRoomEvent`) for visual consistency
  with the Note thread surface.
- Mounts NestsFilterAssemblerSubscription so the wire side stays a
  single REQ regardless of which screen the user opens first
  (`makeLiveActivitiesFilter` already covers 30311/30312/30313/1311
  in one filter).

Wiring:

- Route.MeetingRooms → MeetingRoomsScreen in AppNavigation.
- NavBarItem.MEETING_ROOMS in the catalog (Groups icon, label
  "Meeting Rooms"). Same isDebug gate as NESTS while the surface is
  still in development.
- meetingRoomsFeed in AccountFeedContentStates (mirroring nestsFeed
  for updateFeedWith / deleteFromFeed propagation).
- ScrollStateKeys.MEETING_ROOMS_SCREEN.
- New string `meeting_rooms` ("Meeting Rooms").

https://claude.ai/code/session_01RDpuki4t8StSg1CZcXnV5b
2026-04-27 18:36:10 +00:00
Claude 14953706b6 refactor(nests): split NestActivityBody into themed effect helpers
The 280-line `NestActivityBody` orchestrated VM creation, four event
collectors, two eviction tickers, kick handling, two PIP bridges, the
foreground-service lifecycle, the kind-10312 heartbeat / debounce /
final-leave publisher, AND the screen routing. Each concern was clear
on its own but lost in the wall of LaunchedEffects.

Split into three sibling files, each named for its concern. The body
shrinks to ~70 lines of named composable wiring calls — the room's
lifecycle is now legible end-to-end at a glance.

NestRoomLifecycle.kt — VM + activity bridges:
  rememberNestViewModel(room, signer)
  AutoConnectAndTrackSpeakers(viewModel, onStageKeys)
  LeaveOnKick(viewModel, onLeave)
  PipBridge(ui, pipMuteSignal, viewModel, onMuteState, onConnectedChange)
  NestForegroundServiceLifecycle(ui)

NestRoomEventCollectors.kt — read-side LocalCache → VM:
  NestRoomEventCollectors(viewModel, event, roomATag, localPubkey)
  internally:
    PresenceCollector + PresenceEvictionTicker
    ChatCollector
    ReactionsCollector + ReactionsEvictionTicker
    AdminCommandsCollector  (with the host/moderator authority gate)

NestRoomPresencePublisher.kt — write-side kind-10312:
  NestPresencePublisher(account, event, ui, handRaised)
  internally: heartbeat (30 s) + mute-debounce (500 ms) + onDispose
  final leave on a non-cancellable scope so it survives mid-network
  scope cancellation.
  publishPresence helper relocates here too.

NestActivityContent.kt — now 170 lines:
  - NestActivityContent (entry, parses address)
  - NestActivityBody (orchestration only, ~70 lines)
  - publishPresence helper moved out
  - dropped the half-dozen now-unused imports

Behavior identical: every LaunchedEffect / DisposableEffect ran
before the split runs after, with the same keys, same dispatchers,
same scopes. No new coroutines, no removed.

https://claude.ai/code/session_01RDpuki4t8StSg1CZcXnV5b
2026-04-27 18:25:03 +00:00
Claude f5984e00c5 refactor(nests): collapse 4 per-room subs into one NestRoomFilterAssembler
The room screen used to fan out four parallel relay subscriptions
through separate ComposeSubscriptionManagers — RoomChat, RoomPresence,
RoomReactions, RoomAdminCommands — each opening its own REQ per outbox
relay. They all keyed on the same dimensions (roomATag, account) and
ran in the same place (NestActivityContent), so the multi-sub
fan-out was wire waste.

Replace with a single NestRoomFilterAssembler that issues two Filters
inside one REQ per relay:

  - {kinds: [1311, 10312, 7], "#a": [roomATag]}
      → chat, presence, reactions on the shared a-tag gate
  - {kinds: [4312], "#a": [roomATag], "#p": [localPubkey]}
      → admin commands; #p restriction stays on a separate Filter so
        it doesn't over-restrict the chat / presence / reaction kinds

Net wire change: 4 REQs per relay → 1 REQ per relay with 2 Filters,
same event coverage. Same kinds, same #a/#p semantics, same outbox
relay set.

Files removed:
  RoomChatFilterAssembler.kt
  RoomPresenceFilterAssembler.kt
  RoomPresenceFilterAssemblerSubscription.kt
  RoomReactionsFilterAssembler.kt
  RoomAdminCommandsFilterAssembler.kt

Files updated:
  NestActivityContent.kt — one NestRoomFilterAssemblerSubscription
    call replaces four. The per-kind LocalCache.observeEvents
    LaunchedEffects stay; only the wire side got collapsed.
  RelaySubscriptionsCoordinator.kt — register `nestRoom` instead of
    the four old per-room assemblers.

NestsFilterAssembler (the rooms-list level subscription) is unchanged
— different keying (per-user, per-follow-list), different lifecycle.

https://claude.ai/code/session_01RDpuki4t8StSg1CZcXnV5b
2026-04-27 18:18:48 +00:00
Claude 4796dceb9e fix(nests): respect status / nav bar insets in NestFullScreen
The room content was sliding under the status bar at the top and the
gesture / nav bar at the bottom because the outer Column only had
fillMaxSize + 16dp content padding. With Amethyst running edge-to-edge
the title cropped behind the status bar and the chat composer / Leave
button sat under the system nav bar.

Wrap the content Column in `windowInsetsPadding(WindowInsets.safeDrawing)`
INSIDE NestThemedScope but BEFORE the existing 16dp padding, so:
  - The themed background color + optional `bg` image still paint
    edge-to-edge (NestThemedScope's outer Box stays full-bleed).
  - The room body — title, participants, chat, talk row, Leave —
    is inset within the safe-drawing area.

PIP screen unchanged; the system manages PIP layout itself.

https://claude.ai/code/session_01RDpuki4t8StSg1CZcXnV5b
2026-04-27 18:07:07 +00:00
Claude ad423ead29 fix(nests): keep presence out of channel chat + render kind:10312
Two-part fix for kind-10312 [MeetingRoomPresenceEvent] showing up as
empty cards in the channel chat panel.

(1) Filter at the chat-feed level. ChannelFeedFilter now rejects
MeetingRoomPresenceEvent — these only land in `channel.notes` so the
home live-bubble (HomeLiveFilter) can detect a follow broadcasting
in a Nest by walking channel.notes. They aren't chat content; the
chat panel rendering them as empty rows was a leak from that
home-bubble plumbing.

The filter is in ChannelFeedFilter rather than upstream in
LocalCache.consume because removing presence from channel.notes
would silently break the home-bubble's broadcasting-now detection.
This way the channel keeps the events for non-chat consumers and
the chat just refuses to surface them.

(2) Renderer fallback. NoteCompose now dispatches kind-10312 to
RenderMeetingRoomPresence — a one-line italic status row ("@user ·
raised their hand" / "stepped on stage" / "is speaking" / "joined
as audience" / "left the nest"). After (1) this only shows up on
non-chat surfaces (search results, thread view of a quoted
presence, profile timeline, …), but renders informatively instead
of as the empty card it was before.

Five new strings under nest_presence_*.

https://claude.ai/code/session_01RDpuki4t8StSg1CZcXnV5b
2026-04-27 18:05:02 +00:00
Claude aadf43d5ae Merge remote-tracking branch 'origin/main' into claude/audio-rooms-android-ui-PIpFN 2026-04-27 17:57:03 +00:00
Claude a826766808 fix(nests): hide kind-30312 events missing the EGG-01 required tags
NestsFeedFilter rendered any kind:30312 the relay handed us, including
shells with only a `d` and `a` tag (no room name, no status, no
service / endpoint). The lobby card then drew an empty title row with
a Join button that would 410 unknown_room — confusing UX.

Add a feed-filter gate: a kind:30312 must carry all four of EGG-01
rule 2's required tags before it shows up:

  - room      (display name)
  - status    (any recognized value, including the "live" / "ended"
               legacy aliases nostrnests-web emits)
  - service   (auth sidecar URL)
  - endpoint  (MoQ relay URL)

Closed rooms that DO have all four still render — they may carry a
`recording` tag (EGG-11) and the listen-back affordance is the only
path to that audio post-close.

Sub-rooms (kind:30313 MeetingRoomEvent) get a parallel gate — must
carry a non-blank title.

https://claude.ai/code/session_01RDpuki4t8StSg1CZcXnV5b
2026-04-27 17:53:25 +00:00