Commit Graph
17015 Commits
Author SHA1 Message Date
Claude 3ce9ddd696 refactor(geohash-chat): own the anonymous identity on Account
Replace the standalone GeohashChatIdentity object with an account-owned
GeohashChatIdentityState (account.geohashIdentity), so the throwaway
per-geohash identities are scoped to a single account and cached per cell.

This also fixes a cross-account privacy leak: the old DeviceSeed fallback
(used by bunker / external signers that can't reach a raw key) stored one
seed under a single global preference key, so every account on the device
shared it — producing identical throwaway identities and linking a user's
alt accounts together in every cell. The seed now lives in the account's own
encrypted store (keyed by pubkey), so different accounts get different seeds.

Local-key accounts are unchanged: their identity is still derived from the
account private key and stays stable across devices.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0172JoMccseEKenyWan6txWV
2026-07-16 02:02:09 +00:00
Claude 956385843e refactor(geohash-chat): load room via shared channel feed + datasource
The in-room geohash chat now uses the same data path as every other chat
(public/ephemeral/live/relay-group) instead of a hand-rolled subscription:

- GeohashChatChannel.relays() resolves the cell's geographically-nearest
  relays from a process-wide GeoRelayDirectory.shared (populated by
  GeohashRelays.ensureLoaded), so the subscription layer can reach a cell
  before its first message arrives.
- filterMessagesToGeohashChat wires kind-20000 into ChannelPublicFilterSubAssembler,
  so ChannelFilterAssembler assembles the geohash subscription like any channel.
  Own messages carry the same g tag, so no separate from-user filter is needed.
- GeohashChatScreen loads the feed through ChannelFeedViewModel (LocalCache-backed,
  with mute-filtering for free) + ChannelFilterAssemblerSubscription, mirroring
  LoadEphemeralChatChannel/EphemeralChatChannelView. GeohashChatViewModel drops its
  bespoke client.subscribe and keeps only the geohash-specific composer bits
  (relay resolution for sending, anonymous identity, teleport / post-as-self, PoW).

Rendering stays custom so bitchat nicknames (n tag), the teleport marker, and
anonymous own-message alignment survive — the profile-based shared renderer
can't express those for throwaway per-cell identities.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0172JoMccseEKenyWan6txWV
2026-07-16 01:58:45 +00:00
Claude a18b37e8ca feat(geohash-chat): opt-in "post as my real account"
Adds a per-session composer toggle (off by default) to post to a location channel
under the user's real Nostr identity instead of the anonymous per-geohash key —
trading location privacy for profile, reputation, and zaps. Enabling it requires
confirming a dialog that spells out the location-exposure trade-off. When on, the
message is signed with the account signer (and PoW mined for that pubkey); when
off, the anonymous identity is used as before.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0172JoMccseEKenyWan6txWV
2026-07-16 01:24:59 +00:00
Claude a5c6eeaf03 feat(geohash-chat): map teleport + composer teleport toggle (Phase E)
- LocationPickerMap: an interactive osmdroid map (tap/long-press to pick a
  coordinate) built on the existing display-only map's tile/lifecycle setup.
- GeohashTeleportScreen (Route.GeohashTeleport): tap a spot, pick a precision
  level (region -> building), see the resolved cell + place name, and "Teleport
  here" -> follow the cell (kind 10081) + open its chat with teleported=true.
  Reachable from the builder's new "Teleport to a place on the map" button.
- Route.GeohashChat carries a teleported flag; the chat VM owns the teleport
  state (seeded from the route) and stamps ["t","teleport"] on outgoing messages.
- Composer gains a "Teleport" FilterChip so the user can toggle it per session.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0172JoMccseEKenyWan6txWV
2026-07-16 01:21:19 +00:00
Claude 57933dc64c feat(geohash-chat): Home "live near you" bubble (Phase D) + unify relay directory
- HomeLiveFilter now also surfaces geohash cells with recent (15-min) activity as
  live bubbles. Because geohash chat is anonymous (throwaway per-cell keys), these
  are NOT follow-filtered — any recent message in a cell the user is engaged with
  qualifies (the follow-presence sort simply scores them 0 and orders by recency).
- RenderGeohashBubble: an anonymous location bubble (pin + cell), tap -> the chat.
- Unifies the chat ViewModel onto the shared GeohashRelays directory so the CSV is
  fetched once process-wide instead of per-surface.

Note: a richer "N follows posted near you" signal (from kind-1 geo-notes, the only
npub-linkable location source) is a documented follow-up; this surfaces anonymous
liveliness now.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0172JoMccseEKenyWan6txWV
2026-07-16 01:12:06 +00:00
Claude 6657957677 feat(geohash-chat): native Messages rooms via LocalCache (Phase A+B)
Makes geohash location channels first-class in the Messages tab by routing them
through the same LocalCache -> feed machinery every other room uses:

Phase A (foundation):
- commons GeohashChatChannel : Channel, keyed by the bare geohash, with a
  placeholder-note so a just-joined cell shows before its first message.
- LocalCache: geohashChannels map, get/getOrCreateGeohashChannel, a
  consume(GeohashChatEvent) that routes kind-20000 messages into the cell's
  channel (presence 20001 stays with the live screen), plus getAnyChannel + the
  prune loops.
- GeohashRelays: a process-wide geohash->relay directory (live CSV once, fallback
  otherwise). FollowingGeohashChatSubAssembler + filterFollowingGeohashChats
  subscribe the joined cells (account.geohashList) to each cell's nearest relays,
  registered in ChatroomListFilterAssembler.

Phase B (Messages):
- ChatroomListKnownFeedFilter: a geohashChannels family (feed + incremental
  updateListWith/applyFilter + filterRelevantGeohashChats + geohashRowKey so a
  placeholder and its later real message resolve to one row).
- ChatroomHeaderCompose: a GeohashRoomCompose row (location pin, anonymous —
  name from the message's n tag) -> Route.GeohashChat.
- AccountFeedContentStates: rebuild the list when the joined set changes.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0172JoMccseEKenyWan6txWV
2026-07-16 01:07:48 +00:00
Claude fba41ecd8e feat(geohash-chat): builder + "location channel" chooser entry (Phase C)
Makes location channels reachable and manageable natively:

- quartz GeohashChannelLevel: the named Bitchat precision levels
  (region2/province4/city5/neighborhood6/block7/building8) with cellFor
  truncation, so one location fix yields the whole ladder. Tested.
- LocationState.preciseGeohashStateFlow: an 8-char (building) location flow
  alongside the existing 5-char one (untouched, so the "around me" feed is
  unchanged); channels truncate it per level.
- NewGeohashChatScreen (Route.NewGeohashChat): join a cell from your current
  location (region -> building, with place names via LoadCityName) or by typing
  a geohash. Joining adds it to the kind-10081 geohash list (followGeohash) and
  opens the chat. Includes a "Your channels" section that lists joined cells with
  open + Leave (unfollow) -- the management surface the list previously lacked
  (only the per-cell Follow toggle on the notes screen existed).
- Adds a "Location channel" option to the Messages "+" new-conversation chooser.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0172JoMccseEKenyWan6txWV
2026-07-16 00:45:27 +00:00
Claude c688a3e256 docs: add geohash-list (kind 10081) management UI to the location-chats plan
Records that today the only way to add to the kind-10081 list is the Follow
toggle on the kind-1 geohash notes screen (no arbitrary add, no manage screen),
and folds a "My location channels" manage surface + the builder as the add path
into Phase C.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0172JoMccseEKenyWan6txWV
2026-07-16 00:29:59 +00:00
Claude a1b4d33c8f docs: plan to make location (geohash) chats first-class in Amethyst
Integration plan across Home (live bubble), Messages (rooms list + the new
"+" conversation chooser), and a map-based teleport picker. Reuses the existing
kind-10081 geohash follow list as the "joined location channels" list and routes
geohash chat through LocalCache/feeds. Documents the anonymity constraint (the
"follows are here" signal comes from kind-1 geo-notes, not the anonymous chat)
and the open decisions.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0172JoMccseEKenyWan6txWV
2026-07-16 00:21:51 +00:00
Claude 0ae8316e3b feat(geohash-chat): render messages through the shared chat bubble UI
Re-skins the geohash location chat to use ChatBubbleLayout (the same bubble
component the modernized DM/channel feed uses), so it visually matches the rest
of Amethyst's chats: grouped bubbles by author + time, own-vs-other alignment,
an author line for others, a time footer, and a long-press Copy action. Keeps
the self-contained ephemeral subscription (no LocalCache dependency). The
ViewModel now exposes the per-geohash identity pubkey so own messages align.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0172JoMccseEKenyWan6txWV
2026-07-15 20:48:00 +00:00
Claude 8abfb56440 Merge remote-tracking branch 'origin/main' into claude/bitchat-ephemeral-interop-8epkek 2026-07-15 20:38:14 +00:00
Claude 49ab002d39 feat(geohash-chat): derive the anonymous identity from the account, unlinkably
Geohash chat stays anonymous by default (posting under the real npub would
broadcast the user's location tied to their public identity), but instead of a
random per-device seed the throwaway identity is now derived from the account:

- GeohashKeyDerivation.accountSeed(privKey) = HMAC(privKey, versioned label);
  per-geohash keys are HMAC(seed, geohash). The result is stable across the
  user's devices and recoverable from the account, yet publicly unlinkable to
  their npub and across cells (all one-way).
- GeohashChatIdentity picks the account-derived seed for local-key accounts and
  falls back to the random encrypted device seed for NIP-46/NIP-55 signers whose
  key we can't reach.

Tests cover determinism, account-specificity, and cross-cell unlinkability.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0172JoMccseEKenyWan6txWV
2026-07-15 20:34:12 +00:00
Vitor PamplonaandGitHub 000d01c44e Merge pull request #3579 from vitorpamplona/claude/modernize-chat-rendering-kigcsh
Modernize chat message rendering
2026-07-15 16:27:22 -04:00
Vitor PamplonaandGitHub d37e88f620 Merge pull request #3574 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2026-07-15 16:24:43 -04:00
vitorpamplonaandgithub-actions[bot] 6139923561 chore: sync Crowdin translations and seed translator npub placeholders 2026-07-15 20:10:21 +00:00
Claude 284d7b608a Merge remote-tracking branch 'origin/main' into claude/modernize-chat-rendering-kigcsh
# Conflicts:
#	amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/feed/types/RenderRegularTextNote.kt
2026-07-15 20:08:42 +00:00
Vitor PamplonaandGitHub efa74169b7 Merge pull request #3578 from vitorpamplona/claude/nip29-group-preloading-18kc3e
Prefetch recent group chat messages for list previews
2026-07-15 16:08:19 -04:00
Vitor PamplonaandGitHub 48358603bc Merge pull request #3577 from vitorpamplona/claude/concord-image-display-issue-6qba0w
Extract imeta URL rendering logic to shared utility
2026-07-15 16:06:05 -04:00
Claude 56e3ab5b8b feat(concord): stamp the wrap's seen-on relays onto the decrypted rumor
Like NIP-17's addRelayToNoteAndInners, propagate the relays a Concord plane
wrap was seen on down to its inner rumor, so a received Concord/Armada
message shows the relays it actually came from — not just the channel's
configured relay set.

The wrap note already carries its seen-on relays (added by consumeRegularEvent
before the gift-wrap handler runs), so GiftWrapEventHandler hands them to
concordSessions.ingest, which threads them through the session/registry to the
rumor sink; LocalCache.consumeConcordRumor then stamps them onto the rumor note
after justConsume. Local-echo sends and buffer re-projections pass no relays
(default empty) since there's no per-wrap relay to attribute there.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0129yvP2hmVeDFfuKKy94tqX
2026-07-15 20:00:47 +00:00
Claude 00e5443247 fix(concord): only fill content from imeta URLs when content is blank
Narrow appendMissingImetaUrls to fire only when the message content is
blank: in that case return the imeta URLs joined one per line, otherwise
leave the content exactly as-is. A message that already carries text is
never rewritten, so inline URLs and ordering are preserved untouched.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SF85F6GHu7aQZYpTGNYdWk
2026-07-15 19:53:36 +00:00
Claude c740a346ae fix(chat): timestamp tap opens delivery (not format toggle); relays for Concord
The chat timestamp tap toggled relative/absolute time instead of opening
the relay dialog, because ToggleableTimeAgoText has its own inner click.
Give it a `toggleable` flag and turn it off for the chat time, so the
enclosing tap target opens the delivery dialog; the absolute time now shows
as a header inside that dialog so nothing is lost.

Also:
- Encrypted-channel messages (Concord/Armada) decrypt locally with no
  per-relay attribution, so the dialog showed "no relay information". Fall
  back to the channel's own relays as "where this message lives".
- Move the "Message Delivery" sheet tile out of the always-visible row into
  the Show More section — it's a secondary action.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0129yvP2hmVeDFfuKKy94tqX
2026-07-15 19:47:38 +00:00
Claude bf9fb48205 fix(chat): make the timestamp a reliable "where from" tap target + chip padding
Seeing which relays a message came from was hard to reach: the received
timestamp was only tappable when it had seen-on relays, own messages only
made the tiny tick icon tappable, and the target was a hair-thin text glyph
nested in the bubble's long-press handler.

- Unify the two into ChatTimeWithDelivery: the whole time (+ the own-message
  acceptance tick when we have data) is one padded tap target that always
  opens the relay/delivery dialog. The dialog distinguishes "waiting for a
  relay" (our own pending send) from "no relay information" (old/received).
- Also expose it from the long-press sheet as a "Message Delivery" tile, so
  it's reachable even on messages with no visible timestamp (mid-run).
- Reserve a little bottom space in bubbles that have overlapping reaction/zap
  chips but no footer, so the chips no longer draw over the last line of text.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0129yvP2hmVeDFfuKKy94tqX
2026-07-15 19:34:08 +00:00
Claude 8c9c6e9e90 fix(nip29): preload recent group chat, not just the roster
The joined-groups preload (RelayGroupMyJoinedGroupsFilterAssembler) only
subscribed to roster kinds (39000/39001/39002). It never requested the
kind-9 chat timeline, so the Messages-list preview for a group reflected
only whatever chat events happened to already be in LocalCache from
unrelated subscriptions — reading as "scattered" messages — and the full,
recent timeline was fetched for the first time only when the group was
opened (filterMessagesToRelayGroup, limit 200).

Also prefetch a bounded slice of each joined group's most recent chat
(kind 9 + polls, limit 50, `#h`-scoped, pinned to the host relay) alongside
the roster, so list previews show the true newest message and opening a
group lands on already-cached content. Shares the per-relay EOSE/since with
the roster filter, matching the existing warmup/channel assembler pattern.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016Vvc7LeGNTpg1ggm5P8AtH
2026-07-15 19:32:12 +00:00
Claude 3892ade0a3 fix(concord): render imeta-only images in the chat feed too
The earlier imeta-only fix patched RenderChat (the thread-view path), but
the Concord channel feed renders messages through ChatMessageCompose →
RenderRegularTextNote, which handed the raw decrypted content straight to
the media renderer. So a NIP-C7/Concord image message from an interop
client (e.g. Ditto/Soapbox Armada) — empty content, image carried only as
a NIP-92 `imeta` tag — never rendered in the feed, even though the
encrypted blob's key/nonce were already registered for transparent
decryption at ingest.

Extract appendMissingImetaUrls into a shared helper (ui/note/types/
ImetaContent.kt) and apply it in RenderRegularTextNote against the
decrypted content, symmetric to the RenderChat fix and to
ChannelChat.imageMessage which appends the URL on send. Normal messages
whose imeta URLs are already inline are untouched.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SF85F6GHu7aQZYpTGNYdWk
2026-07-15 19:26:13 +00:00
Claude 80ae26b4cf Merge remote-tracking branch 'origin/main' into claude/modernize-chat-rendering-kigcsh 2026-07-15 18:52:53 +00:00
Vitor PamplonaandGitHub 21b98b3ebf Merge pull request #3576 from vitorpamplona/claude/bottom-nav-item-selection-xt85y9
Bottom nav: pin individual chats/groups + redesigned setup screen
2026-07-15 14:51:19 -04:00
Claude 029f012fe8 feat(nav): pinned group tabs behave as bottom-nav roots
The public-chat, relay-group and Concord-community screens are reached both as
pushed details (from a list) and as pinned bottom-nav tabs. Give them the same
dual-mode chrome the other tab roots use, keyed off nav.canPop():

- Show the back arrow only when there is something to pop. Added an optional
  showBackButton (defaulting to the current behavior) to TopBarExtensibleWithBackButton
  and passed nav.canPop() from PublicChatTopBar and RelayGroupTopBar; gated the
  Concord list screen's back icon the same way.
- Render AppBottomBar in all three screens. It hides itself when canPop, so a
  pushed instance shows the back arrow and no bar, and a bottom-nav instance
  shows the bar and no back arrow.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017mxdSFQcarsKaL179tSub1
2026-07-15 18:40:16 +00:00
Claude 7dc033ec79 fix(ui): show NIP-29 group names in the picker, not their ids
The read-only settings picker never fetches a group's kind-39000 metadata, so with
no cached event RelayGroupChannel.toBestDisplayName() fell back to the raw group id.
Resolve the label from the group's metadata name when loaded, otherwise the name the
user's joined-groups list already stored for it (the NIP-51 ["group", id, relay, name]
tag), and only then the id. Also helps the live bar before 39000 arrives.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017mxdSFQcarsKaL179tSub1
2026-07-15 18:33:19 +00:00
Claude e1a9a5cd7e fix(ui): keep drag alive across swaps + equalize Add/Added pill height
- Drag reorder: key each preview tab by its stable identity. Without a key the
  tabs were position-identified, so when a swap reordered the list, the slot under
  the finger recomposed with a different entry — restarting its pointerInput (keyed
  on the entry) and cancelling the in-flight gesture, so dragging stopped on the
  first swap. Keying moves the dragged composable (and its live gesture) instead.
- Add/Added button: both states now share one Surface + Row body (only color,
  border and tint differ) instead of an OutlinedButton vs a Surface. The
  OutlinedButton's ~40dp min height made "Add" taller than "Added" and broke row
  alignment; a single body keeps the pill height constant.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017mxdSFQcarsKaL179tSub1
2026-07-15 18:12:19 +00:00
Claude 8212e52a50 fix(ui): expand inner options straight down, not diagonally
AnimatedVisibility's default enter (fadeIn + expandIn from the bottom-end) made
the category / chat child lists slide in from the top-left. Switch both to a pure
vertical expandVertically(Top) / shrinkVertically(Top) so the options unroll
straight down with the toggle, matching the accordion's open gesture.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017mxdSFQcarsKaL179tSub1
2026-07-15 16:36:09 +00:00
Claude 0bbda1a92e docs: plan for Bitchat geohash chat interop (phase 1 shipped, DM follow-up)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0172JoMccseEKenyWan6txWV
2026-07-15 16:18:03 +00:00
Claude 5a0f3c8ff1 feat(amethyst): in-app geohash location chat (Bitchat interop)
Adds a self-contained Android surface for Bitchat-interoperable public geohash
chat, reachable from the geohash feed screen's new chat action:

- GeohashChatScreen + GeohashChatViewModel: live subscription to the cell's
  ephemeral kind-20000/20001 events on the geographically-nearest relays, renders
  messages (nickname + teleport marker) with a live participant count, and sends
  kind-20000 messages signed with the device's per-geohash throwaway identity
  plus a small NIP-13 PoW.
- GeohashChatDeviceSeed: device-level random seed (global encrypted storage) all
  per-geohash identities derive from, kept off any account so posting never
  reveals the user's npub.
- Account.signWithAndSendPrivately; Route.GeohashChat + navigation wiring.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0172JoMccseEKenyWan6txWV
2026-07-15 16:17:54 +00:00
Claude b6729bec00 feat(cli): amy geochat listen/send for Bitchat location-channel interop
Adds a thin `amy geochat` verb over the quartz + commons geohash-chat code so the
interop path is exercisable headlessly (and against a real Bitchat client):
`send` builds/signs/mines/publishes a kind-20000 message with the per-geohash
ephemeral identity; `listen` holds a live subscription (kinds 20000/20001 are
ephemeral, so relays broadcast but don't store them) and reports messages plus
distinct present pubkeys; `keys` shows the derived per-geohash pubkey.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0172JoMccseEKenyWan6txWV
2026-07-15 16:17:36 +00:00
Claude ba93dd3564 feat(commons): geohash-relay directory for Bitchat location-channel routing
Adds GeoRelayDirectory, which maps a geohash cell to the Nostr relays closest to
its center so a client lands on the same relays every other client of that cell
uses (the rendezvous rule Bitchat location channels rely on): closest-N by
haversine distance with a host tie-break and :443 dedup, a parser for the public,
MIT-licensed georelays CSV both clients load, a small built-in fallback, and a
jvmAndroid GeoRelayCsvLoader that refreshes the live CSV over the app's OkHttp.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0172JoMccseEKenyWan6txWV
2026-07-15 16:17:23 +00:00
Claude 7df2e240ba feat(quartz): Bitchat geohash chat protocol (kind 20000/20001) + per-geohash identity
Adds the wire-level protocol for interoperating with Bitchat's Nostr location
channels:

- GeohashChatEvent (kind 20000): plain-text public geohash message with a single
  exact ["g", geohash] tag plus optional ["n", nickname] and ["t","teleport"].
- GeohashPresenceEvent (kind 20001): presence heartbeat carrying only the g tag.
- GeohashKeyDerivation: deterministic, unlinkable per-geohash ephemeral identity
  (HMAC-SHA256(deviceSeed, geohash||counter) with retry + SHA-256 fallback).
- Registers both kinds in EventFactory; NIP-13 PoW reuses the existing PoWTag.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0172JoMccseEKenyWan6txWV
2026-07-15 16:17:08 +00:00
Claude 9180061655 feat(ui): drag-reorder inside the preview bar + real favicons
Address two gaps in the redesigned bottom-bar setup screen.

- The preview IS the editor now: the mini nav bar is the reorder & remove
  surface. Drag a tab within the bar to reorder it (holder moveTransient/commit),
  tap its ✕ badge to remove. Drops the separate chip strip — one WYSIWYG bar
  instead of a preview plus a duplicate list.
- Favorites render their real favicon / nsite / napplet icon (via the same
  FavoriteAppIcon + rememberFavoriteIconModel the live bar uses) instead of a
  generic globe glyph — in the preview tabs and the Browser picker rows alike.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017mxdSFQcarsKaL179tSub1
2026-07-15 16:09:01 +00:00
Claude 20fd3e416c Merge remote-tracking branch 'origin/main' into claude/modernize-chat-rendering-kigcsh 2026-07-15 16:03:31 +00:00
Vitor PamplonaandGitHub bfd8ef406c Merge pull request #3575 from vitorpamplona/claude/messages-fab-group-selection-mkddvi
Replace Messages FAB speed-dial with full-screen conversation chooser
2026-07-15 12:01:57 -04:00
Claude f592cbc2ed fix: refine new-conversation chooser copy and audit fixes
- Private Message trade-off now reads "Can't add users later" (accurate
  for NIP-17: you can't add members to an existing DM room).
- Darken the Concord/Public/Disappearing accents so the white icon-tile
  glyph and the white Create-button label meet WCAG AA contrast (the
  amber/teal/orange were ~2.6–3.4:1 on white text).
- Bound the row title/tagline/chip with maxLines + ellipsis so a longer
  localized string can't wrap and break the one-line row layout.
- Mark ConversationType/ConversationSection @Immutable so ConversationRow
  is skippable — an accordion toggle now recomposes only the two affected
  rows instead of all six.
- Fix ProConRow icon modifier order (padding before size) so the check/
  close glyph is nudged, not squished.
2026-07-15 15:47:53 +00:00
Claude 9892ba495b feat(ui): redesign the bottom-bar setup screen — live preview + tab chips
Give the settings screen a point of view instead of a stock Material list.
Presentation only — same BottomBarEntry model, holder and resolver underneath.

- Live preview: a real mini nav bar at the top renders the pinned tabs (first
  one highlighted like the bar on open) and updates as you add, remove and
  reorder — the screen is now WYSIWYG.
- Your tabs: the pinned set is a horizontal, drag-to-reorder chip strip that
  mirrors the bar's own shape, each chip with a leading avatar/icon and an ✕,
  plus an "n / 5" slot hint that turns red past the recommended count.
- Available catalogue: collapsible category cards with a leading icon tile;
  every option row has a tinted circular leading and an "Add → ✓ Added" pill
  instead of a bare switch, so the control states its action and result.
- Group rows show the real group avatar; Browser / Public Chats / Relay Groups
  / Concord expand to your favorites / joined groups (read-only, no REQ storm).

Reorder reuses the holder's moveTransient/commit; all edit logic stays in the
unit-tested BottomBarSettingsState.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017mxdSFQcarsKaL179tSub1
2026-07-15 15:33:29 +00:00
Claude 5e7fffe08e feat: lean, color-coded redesign of the new-conversation chooser
Reworks the "Start a conversation" chooser to be scannable instead of a
wall of text. Each conversation type is now a one-line row — a
color-coded icon tile, the name, a short tagline, and a single chip
naming its deciding axis (1:1–5 rooms / Device-bound / Workspaces /
Unmoderated / Moderated / Live now). Tapping a row expands it in place to
reveal "Best for" and a compact Good / Trade-offs split, then a Create
button that routes to that type's existing flow (accordion: one open at a
time).

Copy now leads with each protocol's real differentiator — Marmot's
device-bound gotcha (chats don't follow an nsec to another app), Concord
as channel-split workspaces, and the relay-aware section grouping Public
Chat + Relay Group by the fact the relay can see and moderate who's there.

Per-type accent colors are lightened in dark theme for legibility; the
solid icon tile keeps the saturated hue in both themes.
2026-07-15 15:15:38 +00:00
Claude 7b93acee0c Merge remote-tracking branch 'origin/main' into claude/modernize-chat-rendering-kigcsh 2026-07-15 15:14:21 +00:00
Vitor PamplonaandGitHub 7743f36ac6 Merge pull request #3573 from vitorpamplona/claude/concord-image-display-ou01vs
Support NIP-92 imeta attachments in chat messages
2026-07-15 11:11:41 -04:00
Claude bb9f0c0cb3 fix(concord): render imeta-only chat images and send thumbhash
Concord/NIP-C7 image messages from interop clients (e.g. Ditto/Soapbox
Armada) attach the image purely as a NIP-92 `imeta` tag and leave the
message content empty. Amethyst's shared renderer only shows media whose
URL appears in the text, so those images never rendered — even though the
encrypted blob's key/nonce were already registered for transparent
decryption at ingest.

RenderChat now appends any imeta URL missing from the content before
handing it to the media renderer (symmetric to ChannelChat.imageMessage,
which appends the URL on send), so imeta-only attachments display.

Also forward the thumbhash on the encrypted-image imeta: FileHeader
computes both blurhash and thumbhash, but only blurhash was being sent.
encryptedImageImeta now emits an additive `thumbhash` field and
toConcordImeta forwards the computed value, so receivers can paint a
placeholder while the blob decrypts.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016TGjBiJL2fvTR37eePBKrG
2026-07-15 14:54:48 +00:00
Claude df313970b1 refactor: extract bottom-bar settings state, share the entry resolver, read-only picker
Quality follow-up to the customizable bottom nav, addressing the audit's three
highest-impact items — behavior unchanged.

- Extract BottomBarSettingsState + the pure BottomBarEditing transforms out of
  BottomBarSettingsContent, so pin/unpin/reorder/restore-default are unit-tested
  (BottomBarSettingsStateTest) instead of only exercisable through the drag UI.
  The composable now just renders and forwards events; moveTransient reorders
  mid-drag and commit() persists once on drag end.
- Unify the phone bottom bar and the navigation rail on one rememberBottomBarSlot
  resolver (route + icon per entry), removing the duplicated built-in/favorite/
  group branches and the divergent selection logic that had drifted between them.
- Give the group resolvers a `subscribe` flag: the live bar/rail keeps a REQ open
  per pinned group (bounded by the few slots), but the settings picker reads
  cached metadata only — so expanding a chat category with many joined groups no
  longer fans out into one relay subscription per row.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017mxdSFQcarsKaL179tSub1
2026-07-15 14:53:20 +00:00
Claude 28fecb9c78 fix(chat): hide delivery ticks on old, untracked messages
Messages sent in a previous session have no delivery-tracker entry, and
frequently no seen-on relays either, so the ticks rendered a "pending"
clock and stayed tappable — implying a days-old message was still waiting
for a relay, and opening a near-empty Message Delivery dialog. When we have
neither tracker data nor seen-on relays there is nothing meaningful to
report, so render no tick at all and just show the timestamp. Messages sent
in the current session keep their live pending -> accepted ticks.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0129yvP2hmVeDFfuKKy94tqX
2026-07-15 14:49:29 +00:00
Vitor PamplonaandGitHub 7de5e75856 Merge pull request #3572 from vitorpamplona/claude/kotlin-compiler-warnings-g10py4
Replace nullable safe calls with immutable map operations
2026-07-15 10:48:30 -04:00
Claude d20cbe0218 fix(chat): don't show an empty "Message Delivery" popup
Tapping the delivery tick on a just-sent message that no relay has
acknowledged yet opened the Message Delivery dialog with only its title:
no tracker entry, no target relays, and no seen-on relays meant every
branch rendered an empty list. Fall back to the pending status line
("Waiting for a relay to accept this message") so the dialog always says
something, and guard the recipient/relay branches against empty lists.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0129yvP2hmVeDFfuKKy94tqX
2026-07-15 14:44:31 +00:00
Vitor PamplonaandGitHub c0f95a5139 Merge pull request #3571 from vitorpamplona/fix/strictmode-loopback-cleartext
fix(strictmode): stop CleartextNetworkViolation noise from the Tor SOCKS proxy
2026-07-15 10:41:37 -04:00
Claude f8f8dd59c2 fix: use non-deprecated PersistentMap.putting()/removing() in RelayAuthenticator
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Myhus2x1c3BSWCtjenSmkf
2026-07-15 14:38:39 +00:00