- Remove the Taproot address block from the onchain card (Copy/Send
actions still operate on the address; it's just no longer displayed).
- Drop the redundant leading spacer above the NWC wallet list so the
gap below the onchain card no longer doubles up.
- Render the Cashu logo in a circular chip mirroring the Bitcoin chip
so both payment rails read as the same kind of object.
- Remove NWC wallet drag-to-reorder: order was purely cosmetic since
a default wallet is always set, so setDefault is the real selector.
Drops moveWallet/moveNwcWallet and the unused wallet_reorder string.
https://claude.ai/code/session_014zaY9EVdxgtdssyVTaeEZd
Pure data class (only @Stable) — extract to
commons/model/nip51Lists/interestSets so Desktop/CLI/iOS can reuse the
interest-set model. Re-points the four interest-set UI files and the
sibling InterestSetsState.
https://claude.ai/code/session_01JFbYZdVV4QmDC4eQYvEccb
Pure data class (only @Stable + quartz bookmark tags) — extract to
commons/model/nip51Lists/labeledBookmarkLists so Desktop/CLI/iOS can
reuse the bookmark-group model. Re-points the six bookmark-group UI
files and the sibling LabeledBookmarkListsState.
https://claude.ai/code/session_01JFbYZdVV4QmDC4eQYvEccb
Extract the three remaining keystone-free quartz-only decryption caches
(MuteListDecryptionCache, PeopleListDecryptionCache,
CommunityListDecryptionCache) into commons/model so Desktop/CLI/iOS can
reuse them. Re-points Account, FeedDecryptionCaches, and the sibling
state holders (MuteListState, PeopleListsState, BlockPeopleListState,
CommunityListState).
The remaining relay-list decryption caches depend on
GenericRelayListCache -> amethyst.model.Note (the keystone), so they
stay until Phase A extracts Note/LocalCache.
https://claude.ai/code/session_01JFbYZdVV4QmDC4eQYvEccb
Pure quartz-only decryption cache — extract to
commons/model/nip51Lists/favoriteAlgoFeedsLists so Desktop/CLI/iOS can
reuse it (mirrors the TrustProviderListDecryptionCache move). Re-points
Account and the sibling FavoriteAlgoFeedsListState.
https://claude.ai/code/session_01JFbYZdVV4QmDC4eQYvEccb
Pure data class (only quartz + a UUID id generator) — extract to
commons so Desktop/CLI/iOS can reuse the NWC wallet entry model.
Swaps java.util.UUID for the multiplatform kotlin.uuid.Uuid (matching
the quartz convention) and re-points the three callers
(LocalPreferences, AccountSettings, WalletViewModel).
https://claude.ai/code/session_01JFbYZdVV4QmDC4eQYvEccb
Replace the labeledEventId/relay/author trio with a single
EventHintBundle<out Event>, building the e-tag via the standard
EventHintBundle.toETag() idiom. The Account caller now passes the
note's event hint directly.
https://claude.ai/code/session_019gc3FipVBcndF9fmqCCfVX
The kind-1985 label subscription for the hashtag feed now follows the
NIP-65 outbox model: instead of querying the flat hashtag relay set for
all labels and filtering to follows locally, it queries each follow's
outbox relays for that follow's own label events (authors restricted per
relay via account.followsPerRelay), tagged with the hashtag. This ensures
a follow's labels are picked up even when they never reach the hashtag's
own relays.
https://claude.ai/code/session_019gc3FipVBcndF9fmqCCfVX
amethyst/service/ByteFormatter duplicates
commons/util/countToHumanReadableBytes. Adopt the commons version as
canonical (it appends a " B" unit suffix for sub-1000 byte counts;
the amethyst copy returned a bare number) and re-point the three
callers.
https://claude.ai/code/session_01H66WwvUYm5KtAWBLgUMcod
amethyst/service/CountFormatter.countToHumanReadable(counter, str)
duplicates the identical 2-arg overloads already in
commons/util/NumberFormatters. Delete the duplicate and re-point the
two relay-row callers.
https://claude.ai/code/session_01H66WwvUYm5KtAWBLgUMcod
amethyst/service/EmojiUtils duplicates commons/util/EmojiUtils
(identical logic; commons uses the KMP codepoint helpers instead of
JVM Character calls). Delete the duplicate and re-point callers.
https://claude.ai/code/session_01H66WwvUYm5KtAWBLgUMcod
CashuToken and Proof are pure data classes (only @Immutable +
kotlinx.serialization). Extract to commons so Desktop/CLI/iOS can reuse
them, and re-point all callers.
https://claude.ai/code/session_01H66WwvUYm5KtAWBLgUMcod
Let users tag any post with a hashtag via a NIP-32 kind 1985 label event
(using the `#t` tag-association namespace), and surface follow-labeled
posts in the hashtag feed.
quartz:
- LabelEvent.buildHashtagLabel() + HASHTAG_NAMESPACE ("#t") and
hashtagAssociations() to build/extract hashtag-association labels.
commons:
- Note now carries a `labels` reverse-reference map (hashtag -> labeler
notes) with addLabel/removeLabel and a NoteFlowSet.labels flow,
mirroring reactions/reports.
amethyst:
- LocalCache consumes LabelEvent, attaching hashtag labels to their
target notes and re-notifying feed observers for already-cached
targets.
- Account.createLabelHashtagEvent/labelHashtag/consumeLabelEvent and
AccountViewModel.labelWithHashtag (tracked + direct broadcast).
- Overflow "⋯" menu gains an "Add hashtag" action backed by a new
AddHashtagLabelDialog.
- HashtagFeedFilter also accepts posts a followed user labeled with the
hashtag; a new label sub-assembler subscribes to kind 1985 by `#l`
and fetches missing label targets.
- Hashtag feed shows an attribution banner ("#tag added by @user") above
follow-labeled posts via a custom RefresheableFeedView onLoaded.
https://claude.ai/code/session_019gc3FipVBcndF9fmqCCfVX
Extract the pure (quartz-only) decryption cache into commons. Converge
the commons trustedAssertions package onto the quartz NIP slug
'nip85TrustedAssertions' (per migration plan §10.2), moving the existing
TrustProviderListState interface + UserCardsCache with it and
re-pointing all callers.
https://claude.ai/code/session_01H66WwvUYm5KtAWBLgUMcod
Pure data class (only quartz + @Stable deps) — extract to
commons/model/nip30CustomEmojis so Desktop/CLI/iOS can reuse it.
Moves the unit test to commons commonTest and re-points callers.
https://claude.ai/code/session_01H66WwvUYm5KtAWBLgUMcod
Adds the architecture end-state (ports exist, impls are forked 3 ways =>
unify), the concrete destination package tree for the migrated common
objects (cache engine, decomposed Account into model/account state +
actions + facade, per-NIP state holders, feeds, keystorage, service split),
and the package-naming decisions behind it (model/nipNN raw state vs
model/account composed view; state/ stays generic; actions=verbs; quartz
slug normalization; LocalCache class+delegating object). Also drops a stray
markup line at the end of the doc.
https://claude.ai/code/session_01KXLzsvx9Gyrm3Yz4Rims55
Survey of amethyst/{model,service,ui} (1817 files) classifying what should
move to commons vs stay Android-native, with per-area matrices, the real
cross-cutting blockers (account state is the keystone; R.string is solved by
Compose Resources, not a new StringProvider; Coil is already KMP), a
dedup/reconciliation backlog, and a phased roadmap with a concrete first PR.
https://claude.ai/code/session_01KXLzsvx9Gyrm3Yz4Rims55
Make the feature-UI vs cross-cutting-UI rule consistent (feature-first):
- ui/nip53LiveActivities -> nip53LiveActivities/ui
- ui/article + ui/editor -> new nip23LongContent/ui (article reader + editor)
ui/ now holds only cross-cutting composables (theme, components, layouts,
elements, markdown, signing, thread, feeds, notifications, screens, state,
text). Tighten ARCHITECTURE.md with the deciding test ('could a second
unrelated feature reuse this as-is?') and reconcile the NIP-second-axis
section so a single-NIP feature owns its UI under <feature>/ui rather than
ui/nipNN.
https://claude.ai/code/session_01KXLzsvx9Gyrm3Yz4Rims55
Rename the two single-NIP feature packages to mirror their quartz
counterparts for 1:1 traceability:
- chess -> nip64Chess
- call -> nipACWebRtcCalls
marmot and nip53LiveActivities already match quartz and are unchanged.
Document the rule in commons/ARCHITECTURE.md: layer is the primary axis,
NIP is the secondary axis (nipNN<slug> matching quartz), and commons is
deliberately NOT reorganized NIP-first at the top level. Also remove
stray markup that leaked into the end of the doc.
https://claude.ai/code/session_01KXLzsvx9Gyrm3Yz4Rims55
fix(sonar-s1871): merge PayInvoice/Nwc error arms via IErrorResponseLike
fix(sonar-s1871): drop redundant is CommentEvent branch in ThreadFeedView
fix(sonar-s1871): merge NPub/NProfile route arms via IPubKeyEntity
fix(sonar-s1871): merge Error/Notice debug-message arms via IRelayDebugMessageText
fix(sonar-s1871): merge LiveActivities/MeetingRoom arms via LiveStreamLike
fix(sonar-s1871): merge channel-message/metadata arms via IsInPublicChatChannel
fix(sonar-s1871): collapse channel-draft reply handling via BaseThreadedEvent
fix(sonar-s1871): drop redundant is CommentEvent arm in NoteCompose
fix(sonar-s1871): merge Failed/Error arms in NIP-05 badge
fix(sonar-s1871): merge Verifying/NotStarted arms in NIP-05 badge
fix(sonar-s1871): merge note-backed RenderOption arms via NoteBackedName
fix(sonar-s1871): drop redundant is PrivateDmEvent arm in RouteMaker
fix(sonar-s1871): merge GiftWrap/SealedRumor arms in RouteMaker via HasInnerEvent
fix(sonar-s1871): merge Connecting/Connected arms in CallSession state collector
fix(sonar-s1871): collapse playback-state when to if/else in CurrentPlayPositionCacher
fix(sonar-s1871): drop redundant is CommentEvent arm in sendPublicReply
fix(sonar-s1871): merge addressable-filter arms via AddressableTopFilter
fix(sonar-s1871): merge repost branches in LocalCache via BaseRepostEvent
fix(sonar-s1871): merge badge-set branches in LocalCache referenced-notes when
Document the commons module's purpose, source-set layout, and the CLI-safe vs
UI boundary in commons/ARCHITECTURE.md, then clean up the clearest package
overlaps that had accumulated:
- merge duplicate util/utils -> util (all source sets)
- unify service/services -> service (jvmAndroid)
- move data/UserMetadataCache -> model/cache
- fold compose/ into ui/ (ui/article, editor, elements, layouts, markdown,
nip53LiveActivities, and Compose helpers in ui/state + ui/text)
- move ProfileBroadcastBanner composable into profile/ui
All changes are whole-file/whole-package moves with import rewrites; no logic
changed. The chess logic/UI split is documented as deferred debt (it needs
file-level surgery, not moves). Marks docs/shared-ui-analysis.md superseded.
https://claude.ai/code/session_01KXLzsvx9Gyrm3Yz4Rims55
R2 — sendNutzap gains an optional preferredMintUrl; the Top-up screen passes the
just-funded selectedTarget so the nutzap spends from THAT mint instead of whichever
shared mint holds the most (which could leave the top-up sitting idle). Falls back
to the best-balance pick when the preferred mint isn't a valid shared target.
R5 — meltToLightning gains skipScrub; rebalance() (which already scrubbed the
source for its coverage check) passes it to drop the redundant second NUT-07
/checkstate round-trip. The Top-up screen's wallet collector now projects to the
per-mint balance map + distinctUntilChanged, so unrelated wallet activity (an
inbound redeem, a scrub, a token for another mint) no longer re-runs the whole
balances/targets/sources rebuild on every global tokenEntries emission.
R7 — replace ReloadMintScreen's hand-rolled copyToClipboard with the shared
Clipboard.setText helper (drops the android ClipData/ClipboardManager imports);
document the keys-only observed values in the reactive railCapability block so a
future reader doesn't delete them as "unused" and silently break the live rail
loading + relay fetch.
https://claude.ai/code/session_01HNE2z7CSYZ2G8KwC5fziJn
addAmount() allowed adding the same amount twice; two preset chips then share the
same key(amount) (Compose duplicate-key hazard) and the drag-reorder's
indexOf(amount) resolves to the wrong chip. De-dupe on add.
https://claude.ai/code/session_01HNE2z7CSYZ2G8KwC5fziJn
Audit found the toppedUp checkpoint was still too coarse: it was set only AFTER
rebalance() / the LN mint fully returned. But funds leave the wallet mid-flow —
the source melt (rebalance) and the invoice payment (Lightning) both happen before
the poll/completeMintFromLightning steps that can throw. A failure there left
toppedUp=false, so "Try again" re-ran the whole move and spent a second time.
- CashuWalletState.rebalance gains an onFundsMoved callback fired immediately after
the melt succeeds; the VM sets toppedUp there.
- The Lightning path sets toppedUp the moment the invoice is confirmed paid, before
issuing ecash.
Either way, once money has moved a retry can only re-send / resume — it can never
move funds again. (The paid-but-unissued quote remains recoverable via the pending
quote banner.)
https://claude.ai/code/session_01HNE2z7CSYZ2G8KwC5fziJn
The zap popup computed rail availability once from a synchronous snapshot, so a
recipient whose lnAddress (kind:0) or nutzap info (kind:10019) hadn't loaded yet —
or before our own cashu wallet finished loading — showed no Lightning/cashu logo
and never updated.
Now it observes those inputs and recomputes railCapability as they arrive:
- observeUserInfo(author) → the Lightning logo appears when the lnAddress loads.
- observeNoteEvent<NutzapInfoEvent>(author.nutzapInfoNote) + the cashu wallet's
mints/tokenEntries flows → the cashu logo appears when the recipient's kind:10019
and our proofs load.
The observers also trigger the relay fetch, so a not-yet-seen lnAddress / kind:10019
gets pulled in while the popup is open.
https://claude.ai/code/session_01HNE2z7CSYZ2G8KwC5fziJn
- Drag-and-drop now works: pointerInput was nested inside the graphicsLayer
translation, so the layer moving under the finger corrupted the per-frame drag
deltas. Moved the gesture outside the transform.
- Preset chip regrouped to match the popup: outlined track, default rail + amount
in a highlighted thumb, alternatives as quiet mono icons, then the X — instead
of everything mashed together.
- Top-up screen header now shows the zap amount between the cashu symbol and the
arrow (you → cashu · N sats → recipient).
https://claude.ai/code/session_01HNE2z7CSYZ2G8KwC5fziJn
Bug: a top-up that moved funds but then failed at the nutzap (the freshly minted
proofs hadn't landed in local state yet → "No proofs available") left a Failed
state; tapping "Try again" re-ran the WHOLE pipeline and moved the funds a second
time — two transfers for one zap.
- Add a `toppedUp` checkpoint set the moment funds land at the target (after
rebalance / completeMintFromLightning). confirm()/retry now skips the move
entirely once topped up and only (re)sends the zap — funds can never move twice.
- awaitTargetFunded(): briefly poll the target balance after topping up so the
follow-up nutzap sees the new proofs and succeeds on the first try instead of
needing a manual retry. Best-effort with a timeout; the checkpoint guarantees no
double-move even if it falls through.
https://claude.ai/code/session_01HNE2z7CSYZ2G8KwC5fziJn
- Toggle border eased from `outline` to `outlineVariant` — present but no longer
heavy.
- Selected cashu (and the reload variant) now tints with the same BitcoinOrange
as the Lightning/on-chain rails instead of the purple accent, so the active
rail colour is consistent across all three.
https://claude.ai/code/session_01HNE2z7CSYZ2G8KwC5fziJn
Reverts the per-segment selected outline. The real ask was a clearer mark for the
entire 3-rail toggle: give the whole component a 1dp `outline` border so it reads
as one control. The selected segment keeps its subtle primaryContainer thumb.
https://claude.ai/code/session_01HNE2z7CSYZ2G8KwC5fziJn
- The new monochrome cashu outline read too small; bump it from 0.72× to 0.86×
the symbol size so it matches the bolt/bitcoin marks optically.
- The selected segment's container fill sat too close to the track to read as
"selected", so add an animated primary outline around the active segment — the
state is now unmistakable (outline + fill + the amount label all land on it).
https://claude.ai/code/session_01HNE2z7CSYZ2G8KwC5fziJn
Replaces the "tap a rail icon = instant send" model (which read like a toggle but
moved money) with an explicit two-step control, so switching rails never sends:
- The rails are one connected segmented pill (shared surfaceVariant track) — they
visibly belong together. A primary-container "thumb" animates to the selected
segment.
- Only the selected segment shows the amount (+ a send arrow); it expands in on the
chosen rail and shrinks away on the previous one, so the amount reads as
travelling to the icon you tapped.
- Tapping an unselected rail only selects it (no payment). Tapping the selected,
labelled segment is the single thing that sends. Selection starts on the
amount-tier default, so the common case is still one tap. Long-press still edits
the presets.
Removes the old leading-icon + trailing circular-button layout and the now-unused
RailButton.
https://claude.ai/code/session_01HNE2z7CSYZ2G8KwC5fziJn
- When a chip has no alternative rails, match the right inset to the left so the
icon+amount pill stays symmetric (was cramped against the right edge).
- Alternative-rail circles shrunk toward the amount's font height (28→22dp) with
a smaller icon inside (ZapRailIcon gained a size param; alternatives render at
14dp), so they stay quiet next to the bigger coloured preferred logo.
- The amount text is now neutral (onSurface) instead of taking the rail's brand
colour — only the leading logo carries colour, so amounts don't shout across
the feed. Dropped the now-unused zapRailAccent helper.
https://claude.ai/code/session_01HNE2z7CSYZ2G8KwC5fziJn
Replaces the per-relay backoff-token approach (opaque WebsocketBuilder
config token + BasicRelayClient bookkeeping) with a much smaller check at
the source.
The connector already holds the two OkHttp clients, and those are rebuilt
only when something connection-relevant changes (Tor's SOCKS port appears,
wifi<->cellular switch). Everything else it wakes on — Tor bootstrap status
churn, connectivity blips, self-heal restarts — leaves the clients
untouched. So instead of threading a config token through quartz, the
connector now forces a backoff-skipping reconnect (ignoreRetryDelays=true)
only when an OkHttp client instance actually changed; otherwise it lets
each relay's exponential backoff decide. That stops the
reconnect-fail-reconnect loop while Tor boots, and still reconnects every
relay (Tor and clearnet alike) the instant the transport changes.
Reverts the quartz/OkHttpWebSocket changes from the previous commit and
wires the connector to take the StateFlows it actually consumes (the two
client flows + connectivity/tor status) instead of the manager objects,
which also decouples it from DualHttpClientManagerForRelays/
ConnectivityManager/TorManager.
https://claude.ai/code/session_01SCz8kdYs2FwesEyzbhmRPY
Bump the pixel sunglasses scale (1.45 -> 1.70) so the shades read bolder and
overhang the slimmed, rounded cashew body more prominently. Body outline and
1.2 stroke weight unchanged.
Long-press a preset chip to pick it up and drag it to a new position in the
FlowRow; release to drop. Hand-rolled (no new dependency):
- VM gains moveAmount(from, to) to reorder the preset list.
- Each chip records its bounds (boundsInParent) and is wrapped in key(amount) so
reordering doesn't restart the in-flight drag gesture; the dragged chip follows
the finger via graphicsLayer translation and lifts with zIndex.
- The drop target is the chip whose bounds contain the pointer; on cross-over the
list reorders and the drag offset is rebased so the chip stays under the finger.
Identity (drag state, bounds map, target) is keyed by the amount value, not the
slot index, to avoid stale-index bugs across reorders.
https://claude.ai/code/session_01HNE2z7CSYZ2G8KwC5fziJn
The Nostr Wallet Connect section (connect button, paste, QR, manual pubkey/relay/
secret) is removed from the zap-amount settings content. NWC setup now lives only
in the wallet area:
- The `nostr+walletconnect` connect deep link (`dlnwc`) now opens Wallet → Add NWC
with the URI prefilled (Route.WalletAddNwc gained an optional nip47 arg), instead
of the old shared NIP-47 setup screen.
- UpdateZapAmountContent loses its nip47uri parameter and the whole wallet-connect
block; both callers (zap settings + NIP-47 setup) updated. The NIP-47 setup
screen keeps its Lightning-address and payment-targets sections (both also
reachable from profile edit / EditPaymentTargets) and is no longer the deep-link
target.
https://claude.ai/code/session_01HNE2z7CSYZ2G8KwC5fziJn
Replace the blocky pixel-stepped cashew silhouette with a smooth rounded
outline (corner-cut and emitted as a compact Bézier spline) so the nut reads
as a clean curved shape instead of a staircase. Use a round stroke cap/join at
the same 1.2 weight; the pixel "deal-with-it" sunglasses stay a solid fill.