The notifications "Global" mode was split into a raw Global (every event
that p-tags the user) and a curated Selected mode. Existing users who had
selected the old curated-Global kept a persisted value that now
deserializes to the much-more-permissive raw Global, so they suddenly saw
everything.
Add a one-shot, per-account migration: on load, if the account hasn't been
stamped yet and its notification filter is Global, rewrite it to Selected
and stamp the account. saveToEncryptedStorage always stamps the account as
migrated, so brand-new accounts (and any deliberate raw-Global choice made
after the split) are never reverted.
Only the notifications key is touched; Global keeps its original meaning
for every other feed.
Reactions and zaps keep the post they target in Note.replyTo so the card
can embed it, but that edge bridges into a different conversation. The
anchorsItsOwnThread guard only covered clicking the like/zap itself, not
clicking a kind-1111 reply to one — so opening such a reply's thread had
searchRoot/loadUp climb reply -> like -> liked post -> ... -> root of the
liked post's thread, dropping the reply into the wrong conversation, and
replyLevel buried it several indents deep.
Treat reactions/zaps as thread boundaries everywhere reposts already are:
- ThreadAssembler.searchRoot stops at a reaction/zap node.
- ThreadAssembler.loadUp adds the node but does not climb its replyTo.
- ThreadLevelCalculator (replyLevel + replyLevelSignature) treats them as
level-0 roots.
The boundary predicate is promoted to a shared Event?.anchorsItsOwnThread().
The data layer (replyTo/replies/computeReplyTo) is unchanged — card
rendering and the notification relevance filter still rely on the link;
only thread traversal stops crossing it.
https://claude.ai/code/session_01LM3KTECMMAdNBHZfs1dANa
Chips render through a selectable Surface that enforces a 48dp minimum
touch target, inflating each chip's measured height above its visible 32dp
pill. That invisible padding dominated the gap between wrapped rows, so the
previous verticalArrangement bump was imperceptible. Disable the minimum
interactive size around the chips so they measure at their visible height
and the row spacing matches the 6dp horizontal spacing.
Each hidden word now displays an inline unblock button when not in
selection mode, mirroring the Blocked Users screen. Long-press to
multi-select still works for batch unblocking.
InputChip clips its avatar slot to a circle, which cut off the following
badge that BaseUserPicture draws slightly outside the picture's circle.
Pass the picture through the leadingIcon slot instead, which is not clipped.
Replace the plain solid-color buttons used for Notify: mentions with
Material3 InputChips that show each user's avatar and display name plus a
remove (X) trailing icon, matching the modern contact-chip pattern. The
add-user action now uses an AssistChip with a PersonAdd icon.
Since the Notifying composable is shared, this updates the look across all
new post screens (short notes, comments/replies, and polls).
Five previews covering the gradient cards: like (+ heart), custom emoji
reaction, lightning zap, cashu nutzap, and onchain zap — each rendered in
the light/dark ThemeComparisonColumn with a hand-built target note so the
embedded makeItShort post shows. RenderLnZap splits into a thin resolver
plus a stateless RenderLnZapCard so the preview can feed the decrypted
sender/amount/comment directly instead of waiting on async decryption.
https://claude.ai/code/session_01LM3KTECMMAdNBHZfs1dANa
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
Captures the verified current behavior (author-keyed DeletionIndex, no
wrap→rumor cascade, accidental seal-id blocking) and the agreed design:
recipient special case in hasBeenDeleted, recipient field on HostStub,
and the reverse-lookup live cascade in LocalCache.
https://claude.ai/code/session_01B39MQmrT3dz137nfpXABvo
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