Extends the onchain zap card's visual language to the other interaction
kinds. A shared ActivityCardFrame draws the rounded gradient wash, with
a circular kind badge, sender → recipient avatars, and a kind pill on
the first line; the makeItShort target post embeds between that line and
the amount, exactly as in the onchain card, followed by the comment.
- Lightning zaps: bitcoin orange, bolt badge, LIGHTNING pill, big sats
amount, decrypted sender avatar.
- Cashu nutzaps: bitcoin orange, cashu badge, CASHU pill.
- Reactions: tinted with the Liked heart rose, heart badge (or the
emoji itself for custom reactions), REACTION pill, no amount row.
- Onchain zaps keep their card; the embedded target post moves inside
it, between the header row and the amount.
The body TransferCard and the trailing reaction emoji are replaced by
the cards; TransferCard stays for its preview.
https://claude.ai/code/session_01LM3KTECMMAdNBHZfs1dANa
Reaction, lightning-zap, and nutzap cards used to render as a note frame
inside a note frame: two identical author headers with the action demoted
to a trailing emoji or a transfer row. They now read as activity cards:
- The action renders as a chip in the author header, next to the actor's
name: the reaction emoji (custom emoji included) or amount-to-recipient
(bolt/cashu icon + sats + recipient avatar). New ActivityActionChip,
wired into FirstUserInfoRow and the thread master header.
- The target post renders as a quoted, bordered, compact embed
(RenderActionTarget, formerly RenderZappedPost) — the only
note-looking frame in the card.
- The zap comment, when present, becomes the card's own text between
header and quote, like a quote-post. The body TransferCard and the
floating reaction emoji are gone.
Onchain zaps keep their gradient tx-status card (already visually
distinct) and only inherit the quoted target styling.
https://claude.ai/code/session_01LM3KTECMMAdNBHZfs1dANa
The Curated (Selected) notification mode gates events through
tagsAnEventByUser, which only recognized a reply by its parent note's
author in the local cache. That made replies to the user's reactions
disappear whenever the reaction event wasn't cached (nothing re-fetches
old kind 7s after a restart), and replies to the user's zaps never
matched at all because zap receipts are authored by the lightning
provider. Detect both from the kind 1111 event itself: reaction parents
via the NIP-22 root/reply author tags, zap parents via the k tag plus
the reply's explicit p tag on the user. Covered by
NotificationTagsAnEventByUserTest.
Also renders the embedded target note inside zap and reaction cards with
makeItShort, keeping the inner preview compact.
https://claude.ai/code/session_01LM3KTECMMAdNBHZfs1dANa
Opening the thread screen on a reaction, lightning zap, nutzap, or onchain
zap now shows that event as the root with only its reply subtree below it —
the post it targets stays visible through the card's embedded preview, but
the target's conversation no longer loads around it. Replies and comments
(kind 1 / 1111) keep the existing behavior: the full parent thread loads
and the screen lands on the clicked note. Covered by ThreadAssemblerTest.
The long-press reply shortcut on notification zap chips is removed: the
thread view's reply button is now the single reply entry point (and still
routes private zaps to the sender's DM room via routeReplyTo).
https://claude.ai/code/session_01LM3KTECMMAdNBHZfs1dANa
ScreenshotsStrip now renders each screenshot through ZoomableContentView,
so tapping one opens the standard fullscreen zoomable viewer with paging
across all of the app's screenshots. Tiles keep a fixed height and take
their width from the image's cached aspect ratio (portrait fallback
before first load).
https://claude.ai/code/session_01RJSke6d4dpSZgdtXAmGMmp
RenderLnZap, RenderNutzap, and RenderOnchainZap now show the post the zap
targets above the transfer card (via the zap note's replyTo, mirroring
RenderReaction), so the target is visible wherever a zap renders — the
thread master view, composer reply previews, and quoted embeds. Reactions
already embedded their target.
https://claude.ai/code/session_01LM3KTECMMAdNBHZfs1dANa
Clicking a zap, nutzap, or like chip in the notification galleries now
navigates to that event's own thread, where anyone can reply, boost, zap,
or share it — the sender's profile remains one tap away via their avatar
in the thread header. Boost chips keep navigating to the profile.
To make those threads render properly:
- The thread master view now dispatches ReactionEvent (kind 7) through
RenderReaction and NutzapEvent (kind 9321) through a new RenderNutzap
transfer card; NoteCompose gains the NutzapEvent branch as well.
- The master header shows the zap sender (from the embedded zap request)
instead of the lightning provider that signed the receipt, including
the avatar click target.
The private-zap reply-via-DM fallback moves from the chip long-press into
routeReplyTo, so every reply entry point — including the thread view's
reply button — routes private zaps to the sender's DM room when we hold
the decrypted sender, instead of a public composer that cannot tag them.
https://claude.ai/code/session_01LM3KTECMMAdNBHZfs1dANa
AppIcon used to render an empty bordered box when the icon url was
missing, still downloading, or failed to load. It now draws the app
name's first letter on a surfaceVariant background underneath the
image, so all three states show a meaningful placeholder.
https://claude.ai/code/session_01RJSke6d4dpSZgdtXAmGMmp
Feed cards now render the app's screenshots in a horizontal LazyRow at
200dp so the images are big enough to convey what the app is about.
The platform/license chips were removed from the card since they add
little at feed level (still shown on the detail screen).
https://claude.ai/code/session_01RJSke6d4dpSZgdtXAmGMmp
Replaces the standalone author row on the detail screen with a shared
AppAuthorLine ('by' + clickable profile picture + username) rendered
right under the app name, and adds the same line to the app feed cards.
https://claude.ai/code/session_01RJSke6d4dpSZgdtXAmGMmp
With WhileSubscribed, nothing held a strong reference to my own 31989
notes while no UI was collecting; under memory pressure the soft cache
could drop them, and the next recommendApp would rebuild that kind's
event from an empty snapshot, wiping previously recommended apps.
Eagerly keeps the observer registered (its result set strongly
references the notes), matching the sibling account states.
https://claude.ai/code/session_015dX5vWqvXUYD8rzPYX8vTB
Follows the Account state-class convention (InterestSetsState et al.):
the observed flow, the publish mutex, and recommendApp/unrecommendApp
now live in model/nip89AppHandlers/AppRecommendationsState, publishing
through account.sendMyPublicAndPrivateOutbox. Account just links it as
val appRecommendations = AppRecommendationsState(signer, cache, scope).
https://claude.ai/code/session_015dX5vWqvXUYD8rzPYX8vTB
- Add 12dp horizontal padding to all app detail sections (header,
screenshots, about, platforms, topics, links, releases) so content
no longer touches the screen edges like other screens
- Render comments with the reply-level indentation bars used by the
regular thread feed (drawReplyLevel + levelFlowForItem)
- Move the reactions row below the releases, matching the master-note
layout of thread screens, and follow it with a divider
- Drop the Comments section label since the reactions row + threaded
replies already follow the standard screen pattern
https://claude.ai/code/session_01RJSke6d4dpSZgdtXAmGMmp
Account.myAppRecommendations is now LocalCache.observeEvents (indexed
kind+author filter) stated in the account scope, seeded with a
synchronous scan so the editor's first frame still sorts correctly.
The editor and the Recommend button collect it instead of rescanning
the cache on every newEventBundles emission; isAppRecommended is gone.
The synchronous scan stays only as the seed and for the mutex-guarded
read-modify-write publishers, which must read current cache truth.
https://claude.ai/code/session_015dX5vWqvXUYD8rzPYX8vTB
Offers can be configured (via nmanage / ShockWallet) to require payer
fields; Lightning.Pub rejects requests missing them with the misleading
"Invalid Offer" (code 1) reply. A flag to attach payer_data makes such
offers testable from the CLI.
https://claude.ai/code/session_01Fh7MRv8477pJiAJZ7yF87r
Replaces TopFilter.GlobalRaw: TopFilter.Global itself now shows every
event that p-tags the user in Notifications (still minus hidden/reported
authors, muted threads, hidden DM content, and self), and the curated
per-kind relevance heuristics move to a new notifications-only
TopFilter.Selected mode.
Selected is offered only in the notifications top nav filter and is the
default for new users and new installs. Existing users who had Global
selected keep Global and therefore now see everything, intentionally.
https://claude.ai/code/session_013VDWpD8Dr6sBF7tBEUZGpg
Clicking an app in the recommendations editor (or any 31990 link)
navigated to Route.Note(event.id) — the per-id version note, which the
soft cache evicts and relays can't serve for replaceables — leaving the
thread stuck on 'Event is loading or can't be found'. Route by
addressTag() like the generic AddressableEvent branch already does.
https://claude.ai/code/session_015dX5vWqvXUYD8rzPYX8vTB
Searching for an app name (e.g. "Amethyst") was returning every event
published through that client, because the local-cache note search
matched the search term against all tag values including the NIP-89
["client", ...] tag. Skip the client tag when matching tag values in
findNotesStartingWith.
https://claude.ai/code/session_01YMs6aXuvs5NaYjzyPH6Zqj
The notifications mode previously labeled Global applied per-kind relevance
heuristics (tagsAnEventByUser) that drop reactions/reposts targeting other
people's notes, unrelated thread replies, etc. That mode is now shown as
"Selected" in the notifications spinner, and a new real Global mode
(TopFilter.GlobalRaw) shows every event that p-tags the user, filtered only
by the notification kind whitelist, hidden/reported authors, muted threads,
muted DM content, and self-authored events.
The split-notifications Everyone tab now pins to the raw Global mode.
Other feeds' Global filter is unchanged.
https://claude.ai/code/session_013VDWpD8Dr6sBF7tBEUZGpg
- New ByAuthorChip ('by <picture> <name>' pill, tappable to the profile,
with the following-checkmark overlay on the picture) shown under the
app name in the recommendations editor rows and on the app definition
card, so apps from known authors are distinguishable from impersonators.
- The editor's ordering pins now initialize from cached data instead of
empty sets, so the first frame after returning from an item sorts the
same as before leaving; previously the restored scroll anchor jumped
down the list when the recommended/follows tiers kicked in.
https://claude.ai/code/session_015dX5vWqvXUYD8rzPYX8vTB
- Account.kt: collapse three identical backend-not-configured Failure
constructions into one helper
- OnchainZapSendError: declare causeIsUserFacing on the enum so the
sender owns which failures carry a human-readable cause, instead of
the UI mapper hardcoding the list
- SendPaymentScreen: fee chip label is now a single format resource
instead of manual string concatenation
Source-identical values (Cashu, Lightning, On-chain, and per-locale
loanwords like Meditation/Yoga/Workouts in de) are intentionally left
to the English fallback since Crowdin strips them on export anyway.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>