Commit Graph

12608 Commits

Author SHA1 Message Date
Vitor Pamplona 5ec25c6013 Merge pull request #2676 from vitorpamplona/claude/fix-chat-scroll-behavior-Ig1YE
Auto-scroll to newest messages in chat when new messages arrive
2026-04-30 21:17:40 -04:00
Claude f6f2559d31 Auto-scroll nest chat to newest message when at bottom
Both NestFullScreen and NestLobbyScreen render the kind-1311
transcript with LazyColumn(reverseLayout = true). When a new message
is prepended, LazyColumn preserves visual position by shifting
firstVisibleItemIndex from 0 to 1, leaving the new message just below
the viewport — the user has to scroll down to see it.

If the user is at (or one item from) the bottom when a new message
arrives, scroll back to item 0 so the newest message is visible. If
they are reading older history, leave them where they are.
2026-04-30 23:59:25 +00:00
Vitor Pamplona 6ee15ee6ec Planned rooms should go to the lobby 2026-04-30 19:44:24 -04:00
Vitor Pamplona 9ff9a4b4e2 Better align the scheduled flag 2026-04-30 19:41:20 -04:00
Vitor Pamplona bad3cfa8f6 Makes sure relays stay connected in the room activity 2026-04-30 19:33:52 -04:00
Vitor Pamplona 55ec5b910b Merge branch 'main' of https://github.com/vitorpamplona/amethyst 2026-04-30 19:22:05 -04:00
Vitor Pamplona cc194273a3 Passing the Note, not the event to the Nest viewmodel 2026-04-30 19:20:53 -04:00
David Kaspar abe1043c8e Merge pull request #2668 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2026-05-01 01:12:42 +02:00
Vitor Pamplona 2fe585096f Fixes after merge 2026-04-30 19:06:27 -04:00
Vitor Pamplona 5c78b0dfcd Merge 2026-04-30 19:05:44 -04:00
Crowdin Bot cbc2a5943a New Crowdin translations by GitHub Action 2026-04-30 23:02:38 +00:00
Vitor Pamplona 39344afe81 Merge pull request #2675 from vitorpamplona/claude/prevent-lock-nest-screen-GKcSS
Keep screen on during active nest room connections
2026-04-30 19:00:56 -04:00
Vitor Pamplona 83913f743d Merge pull request #2674 from vitorpamplona/claude/fix-nestfeedcard-lobby-vvoVP
Gate Nest room launches by liveness status
2026-04-30 19:00:36 -04:00
Claude 4715ae6b66 Keeps the screen on while connected to a Nest room
Holds FLAG_KEEP_SCREEN_ON via the existing KeepScreenOn composable
while ConnectionUiState is Connected, so the device doesn't lock
and interrupt an active audio-room session.
2026-04-30 22:28:33 +00:00
Claude 897e036b3a fix(nests): only gate room re-entry through lobby when card reads closed
NestFeedCard previously routed every tap on a joinable MeetingSpaceEvent
through NestLobbyScreen. The lobby exists to keep an accidental tap on
a stale room from booting the audio pipeline (and the host-side
kind-30312 republish path) — once the badge says the room is live with
fresh presence, that gating is just extra friction. Mirror the badge's
liveness heuristic in the click handler and launch NestActivity
directly for cards rendering as LIVE / PLANNED / PRIVATE; only EndedFlag
cards (status = ENDED, status = LIVE with stale presence, or unknown
status) keep flowing through the read-only lobby.
2026-04-30 22:25:39 +00:00
Vitor Pamplona 3dceb194dc Removes the disappearing bar from the TwoPane messages 2026-04-30 17:59:28 -04:00
Vitor Pamplona fa8892b6a4 Merge pull request #2673 from vitorpamplona/claude/fix-chatroom-list-reuse-Wkz10
Fix double padding in two-pane chat layout
2026-04-30 17:46:45 -04:00
Vitor Pamplona 4c4a28dbe9 Removing unnecessary label 2026-04-30 17:42:44 -04:00
Vitor Pamplona 4003b735dd Adds an isNewThread to the TextNote 2026-04-30 17:40:16 -04:00
Vitor Pamplona 2f9c6bddeb simplifying the readme 2026-04-30 17:37:29 -04:00
Claude 089261420c refactor(chats): apply scaffold padding to two-pane left pane
Move the workaround out of ChatroomList: use the DisappearingScaffold's
padding lambda parameter (previously unused) to position the left pane
below the top bar and above the bottom bar. Reset
LocalDisappearingScaffoldPadding to zero only at that boundary so the
inner LazyColumn inside ChatroomListFeedView doesn't double-count the
bar inset and produce a gap below the TabRow.

The right pane keeps systemBarsPadding because ChatroomView /
PublicChatChannelView bring their own DisappearingScaffold; layering the
outer padding there would stack the chat header below the outer search
bar.

ChatroomList stays a flat Column with no scaffold awareness.
2026-04-30 21:32:10 +00:00
Vitor Pamplona 479f14cbd4 additional info 2026-04-30 17:28:20 -04:00
Vitor Pamplona acb36adfa3 Merge branch 'main' of https://github.com/vitorpamplona/amethyst
# Conflicts:
#	quartz/README.md
2026-04-30 17:26:28 -04:00
Vitor Pamplona 43dc925b74 Updates readme to look more like a guide 2026-04-30 17:22:58 -04:00
Claude 2db8dc99a8 fix(chats): drop scaffold top inset for inner feed in two-pane
In MessagesSinglePane the MessagesTabHeader sits inside the
DisappearingScaffold's topBar slot, so LocalDisappearingScaffoldPadding's
top component already covers the bar + tabs and the inner LazyColumn's
contentPadding lines the first row up flush below the tabs.

In MessagesTwoPane the tabs are part of the column content rather than
the scaffold's top slot. The scaffold still publishes its top-app-bar
height as the top inset, so rememberFeedContentPadding inside the feed
double-counts it and renders a top-app-bar-tall gap between the TabRow
and the first chatroom row.

Override LocalDisappearingScaffoldPadding around MessagesPager to zero
the top component while preserving start/end/bottom, so the feed still
clears the bottom bar.
2026-04-30 21:15:54 +00:00
Vitor Pamplona 4c9ccbadbd Merge pull request #2672 from vitorpamplona/claude/quartz-docs-update
Refactor relay integration docs: EventCollector pattern and lifecycle management
2026-04-30 16:57:10 -04:00
Vitor Pamplona 6aa24c5ef2 Merge pull request #2671 from vitorpamplona/claude/fix-nestlobby-layout-ofVC0
Refactor NestLobbyScreen layout: move header outside LazyColumn
2026-04-30 16:56:35 -04:00
Claude 61bed279fc fix: lift NestLobby header out of the chat LazyColumn
The room metadata (RoomHeader) and live listener count were items
inside the reverse-layout LazyColumn, so they scrolled with the chat
and forced the chat to share its viewport. Move them to a regular
Column above the LazyColumn so the chat list owns most of the screen
and the header sits as a fixed row on top.

Drop the redundant "Recent chat" label that was only there to
separate the in-list header from messages.
2026-04-30 20:50:53 +00:00
Claude c4a38bb899 docs(quartz): align tutorials with the layered patterns demoApp uses
The README's "Wiring relays into the store" tutorial used a
per-subscription SubscriptionListener.onEvent callback to feed the
store. EventCollector landed exactly to remove that boilerplate, so
update the example to register one connection-level collector at app
init and stop wiring listeners per subscribe() call.

The "Building a reactive feed UI" example showed project() ->
stateIn(WhileSubscribed) but never opened the relay subscription.
Bind it to the projection's collection lifecycle with .onStart {
client.subscribe } / .onCompletion { client.unsubscribe }, so the
relay subscription's lifetime equals the UI collector's. Also
demonstrate ProjectionState.filterItems for narrowing without a
re-query.

Drop the redundant client.connect() call from the wiring example
and add an explicit note (in both README and CLIENT) that
NostrClient connects on demand from subscribe()/publish() — connect()
is only useful after disconnect().

Other small fixes:
  - client.send(...) -> client.publish(...) (the actual API name).
  - Rename the variable from `observable` to `db` to match the demoApp
    code and the conceptual "this is the local database" framing.
  - Wrap the publish path in a viewModelScope.launch so the snippet
    is copy-pasteable into a ViewModel.
2026-04-30 20:50:00 +00:00
Vitor Pamplona 276b25a448 Removes the stop broadcast button, user must leave the stage to stop 2026-04-30 16:38:47 -04:00
Vitor Pamplona 2a0ea98df2 Merge pull request #2670 from vitorpamplona/claude/update-nestlobby-chat-layout-JimqW
Claude/update nestlobby chat layout jimq w
2026-04-30 16:22:38 -04:00
Claude 8f4474c8a7 fix(nests): make Open lobby action a prominent filled button
A TextButton in the AppBar actions slot read as too subtle and small
next to the title. Switch to a filled Button so the primary CTA is
unmistakable. The actions Row already centers children vertically;
trim the trailing modifier padding so the button sits flush with
the bar's edge.
2026-04-30 20:13:28 +00:00
Claude 8f7c0eac47 refactor(nests): lobby chat reuses NestFullScreen list shape
Replace the static Column-based CachedChatList with the same
LazyColumn(reverseLayout = true) the in-room screen uses
(NestChatPanel.NestChatMessageList). Items are keyed by note id,
and the room metadata header / listener count / "Recent chat"
label are stacked above the oldest message so the user can scroll
up through history into the room info.

Wire onSendNewMessage to animateScrollToItem(0) so a freshly sent
message snaps the list back to the newest entry — same behaviour
the in-room composer has.
2026-04-30 20:12:45 +00:00
Vitor Pamplona c249765c51 Merge pull request #2669 from vitorpamplona/claude/update-nestlobby-chat-layout-JimqW
Add chat composer to nest lobby screen
2026-04-30 16:04:02 -04:00
Vitor Pamplona 12ed2af028 Moves java.util.SortedSet to an arrayList. 2026-04-30 15:57:26 -04:00
Claude 02d34ed74f feat: active chat composer on NestLobby with Open action in top bar
- Replace bottom Open Room button with a TextButton "Open" action in
  the top app bar, freeing the screen bottom for the chat composer.
- Mount NestEditFieldRow + NestNewMessageViewModel on the lobby so
  users can post kind-1311 messages without joining the audio plane;
  in-room composer features (mentions, file upload, drafts, emojis)
  carry over.
- Switch the Scaffold to contentWindowInsets=0 and use imePadding +
  navigation-bar inset on the composer so the chat list draws behind
  the 3-button system navigation while the composer stays reachable
  above it (and rises above the IME when the keyboard opens).
- Drop the now-unused OpenNestRoomButton helper and its string.
2026-04-30 19:53:41 +00:00
Vitor Pamplona 728a396ff2 Merge pull request #2667 from vitorpamplona/claude/fix-mute-button-height-6Vfip
Refactor NestActionBar UI layout and state handling
2026-04-30 15:24:13 -04:00
Claude 951f6c37f0 refactor(nests): extract action bar affordances and refresh KDoc
NestActionBar.kt had grown to ~470 lines with most of the volume in
OnStageControls, where four broadcast-state branches each inlined a
56dp filled icon button, a tonal toggle button, and an outlined
"Leave the Stage" button. The result was hard to scan for state
coverage, and the top-level KDoc still described the pre-cleanup
layout.

Changes:

* Rewrote the top-level KDoc as a state-by-state table matching the
  current UI.
* Extracted reusable affordances:
  ConnectButton, StatusChip, LeaveStageButton, TalkButton,
  StopBroadcastButton, MicMuteToggle, HandRaiseToggle,
  LeaveRoomButton.
* Merged the duplicate Connect button branches in StartCluster
  (Idle / Closed / Failed all share one). The failure reason already
  appears in the status strip above.
* Split OnStageControls: kept the dispatcher thin and moved the
  permission state + Settings deep-link into OnStageIdleControls,
  which is the only state that needs them.
* Replaced the inline qualified Settings Intent with a
  Context.openAppSettings() helper, plus a Context.hasMicPermission()
  helper for the two RECORD_AUDIO checks.
* Pulled the status-strip text resolution into a small
  NestUiState.statusStripText() so ActionBarStatusStrip is one Text.

No behavior changes — just structure, naming, and docs.
2026-04-30 19:22:52 +00:00
Claude 7be8ac5106 fix(nests): drop redundant Live chip while broadcasting
The red Mic-on FilledIconButton already conveys "you are live, tap
to stop" — the AssistChip labelled "Live" beside it was duplicate
visual noise. Removed the chip and its now-orphan string. Mute toggle
+ red Mic + Leave the Stage are sufficient to express the broadcasting
state.
2026-04-30 19:14:17 +00:00
Claude 640e7904e4 fix(nests): tighten action bar state coverage
Several gaps in the action bar were causing dead-end UI states or
making non-actionable controls visible:

* Hand-raise was rendered while disconnected / connecting / failed.
  Raising can't be delivered to the room until we're Connected, so
  hide it until the connection state agrees. Threaded `isConnected`
  into `EndCluster` from the root composable.
* Audience listen-mute toggle (Volume Up/Off) removed. System volume
  keys cover local volume; the on-screen control was redundant and
  shared the same speaker glyph as the broadcasting mic-mute toggle,
  which made the two affordances easy to confuse.
* On-stage user without `canBroadcast` had no way to step down without
  leaving the whole room. Show a "Leave the Stage" outlined button
  in that branch so the speaker slot can be released.
* Broadcast `Connecting` and `Failed` substates also had no
  step-down affordance — forcing the user to either retry the mic or
  leave the room. Added "Leave the Stage" to both. `stopBroadcast()`
  cancels the in-flight `speakerConnectJob` (NestViewModel
  `teardownBroadcast`), so cancelling mid-handshake is safe.
* `BroadcastUiState.Broadcasting.muteError` was tracked in state but
  never surfaced. Route it through `ActionBarStatusStrip`.
* Permission denial pill said "Open settings"; renamed to
  "No permissions" so the label communicates the *state* (the tap
  still deep-links to the system settings page).
2026-04-30 19:02:25 +00:00
Vitor Pamplona 48523c2903 Merge pull request #2666 from vitorpamplona/claude/fix-nest-room-status-3Mmq9
Add NestLobbyScreen for read-only room preview before joining
2026-04-30 14:04:35 -04:00
Vitor Pamplona 2b4064df96 Merge pull request #2665 from vitorpamplona/claude/tor-connection-fallback-uJM2E
Add Tor connection timeout with graceful fallback to regular connection
2026-04-30 14:03:10 -04:00
Claude b86219b1e1 fix(nests): match mute toggle height with action bar buttons
Material3 expressive defaults the FilledTonalIconToggleButton container
shorter than ButtonDefaults.MinHeight, leaving the audience listen-mute
and on-stage mic-mute toggles visibly shorter than the neighboring
"Leave the Stage" / "Leave" outlined buttons. Pin the toggle height to
ButtonDefaults.MinHeight so the row aligns.
2026-04-30 17:57:45 +00:00
Claude 786864c311 feat(tor): fall back to clearnet when bootstrap is stuck
When Tor stays in Connecting for >60s, prompt the user to drop the proxy
for the rest of the session. The choice is persisted as a 1-hour window
so subsequent failures (across cold starts, network changes) silently
fall back without re-prompting; after the hour the dialog returns.

Bypass clears on user-initiated TorType changes and on network identity
changes so Tor gets a fresh attempt under new conditions.
2026-04-30 17:47:47 +00:00
Vitor Pamplona 1e4829626b Merge pull request #2664 from vitorpamplona/claude/quartz-eventstore-observer-o2f1O
Add event projection and interning for reactive event store queries
2026-04-30 13:46:40 -04:00
Claude 4e5a69da66 feat(nests): in-app lobby route to gate room re-entry
Adds Route.NestLobby — a regular Compose destination — between every
Nests entry point (feed card, channel-view card, in-feed CTA, naddr
deep links) and NestActivity. The lobby is read-only: it warms cached
chat, host info, listener count, and recent messages via the existing
NestRoomFilterAssemblerSubscription, but never opens a NestViewModel,
publishes kind-10312 presence, or launches MoQ. Only the new
OpenNestRoomButton inside the lobby launches NestActivity, so a user
coming back to peek at an old room no longer triggers the audio
pipeline (or the host-side kind-30312 republish path) by accident.

JoinNestButton now navigates to the lobby instead of launching the
activity; OpenNestRoomButton owns the actual launch and lives only
on the lobby screen.
2026-04-30 17:41:58 +00:00
Claude c873385fd6 docs(quartz): add module-level README with layer overview + tutorials
Tutorial-style README covering the full pipeline:
relay → NostrClient → ObservableEventStore → InterningEventStore →
EventStore → project() → ViewModel → Compose.

Two worked examples:
1. Wiring NostrClient subscriptions into the store via
   SubscriptionListener.onEvent.
2. Building a reactive feed UI with project().stateIn(...) and
   Compose, showing how the three reactivity layers (Loading/Loaded
   state, list membership, per-event handles) map to Compose's
   recomposition model.

Cross-links the existing CLIENT.md, RELAY.md, and store/sqlite
README. Pointers to per-class KDoc for projection internals.

https://claude.ai/code/session_01Jny85MTu1ynKgFBgysfWu5
2026-04-30 17:28:17 +00:00
Claude 4cfd56da36 perf(quartz): cheaper hot paths in EventStoreProjection
Three small wins on paths the projection runs every event arrival
or every snapshot publish:

1. Cache `address` on Slot. Previously `removeIndexes(slot)` called
   `addressOf(slot.flow.value)` on every drop — for replaceables
   that's a fresh Address allocation every time. Now the address is
   computed once at slot construction and reused on removal.

2. Single-filter snapshot fast path. The deduped sorted union via
   TreeSet was unnecessary when there's only one per-filter set —
   that set is already sorted in the slot comparator's order. The
   common UI case (one filter per projection) now skips the
   TreeSet allocation + log-n inserts entirely.

3. Comparator singleton. `slotComparator()` was allocating a fresh
   Comparator lambda on every call (ctor + every snapshot).
   Replaced with a single `Comparator<Slot<*>>` cast at the use
   site — the comparator only reads sort keys that don't depend on
   the type parameter.

Also trimmed a verbose comment block in `project()` (3 lines → 1).
All 18 projection tests + 240 store + interner tests still pass.

https://claude.ai/code/session_01Jny85MTu1ynKgFBgysfWu5
2026-04-30 16:24:15 +00:00
Vitor Pamplona 3d46dfcfe1 Merge pull request #2663 from vitorpamplona/claude/fix-nest-room-crash-KJwBQ
Support Android's hostname-aware certificate validation
2026-04-30 12:16:25 -04:00
Claude 058c56dc21 feat(quartz): re-evaluate filter membership on addressable supersession
When a replaceable / addressable update arrives in handleInsert, the
projection now re-runs Filter.match against the new event for every
filter. A v2 that no longer matches a filter (e.g. tag list changed)
is removed from that filter's set; a v2 that newly matches a filter
joins it. If no filter retains the slot afterwards, it's fully
dropped from byId / byAddress.

Closes the previous gap where v2 of an addressable kept a stale
filter membership inherited from v1. The slot's MutableStateFlow is
still updated in place — collectors of that handle still see the
content change before the membership update emits.

The cap-eviction-with-cleanup logic was extracted into a small
`admit` helper since the supersession path and the new-slot path
both need it.

For the common case (filter on `kinds + authors`, no tag/time
constraints), v2 always still matches — the new branches are no-ops
and the list reference stays stable, preserving the in-place update
guarantee. The behaviour change kicks in for tag, time-window, or
id-list filters.

New test addressableUpdateDropsSlotWhenFilterStopsMatching: v1 has
hashtag "nostr" and matches a `t = nostr` filter; v2 changes to
"bitcoin"; the projection drops the slot. 18/18 projection tests
pass.

https://claude.ai/code/session_01Jny85MTu1ynKgFBgysfWu5
2026-04-30 16:10:47 +00:00