Commit Graph
15076 Commits
Author SHA1 Message Date
Claude 1c2775c6b5 fix(quartz): emit q tags again (inverted guard dropped all quotes)
TagArrayBuilder.addUniqueValueIfNew had an inverted guard:

    if (tag.has(1) || tag[0].isEmpty() || tag[1].isEmpty()) return this

Since has(index) == size > index, `tag.has(1)` is true for every
well-formed tag with a value, so the function returned early and never
added it. addUniqueValueIfNew / addAllUniqueValueIfNew are used only by
the quote() / quotes() builders, so every `q` tag (naddr, nevent, note,
nembed, npub, nprofile) has been silently dropped since this file was
introduced. Restore the missing `!` and add a regression test covering an
addressable (naddr) quote plus the guard's accept/skip/dedupe semantics.

https://claude.ai/code/session_01NMavNzJ7VRLhoD3hboCCC7
2026-06-01 19:15:03 +00:00
Claude 0fb6f6778d refactor: one DM window, NIP-04 followers, shared listener
Collapse the DM windowing system to a single source of truth and remove the
accumulated duplication.

- One window: the gift-wrap (NIP-17) loader owns the only TimeWindowPagination.
  The rooms-list NIP-04 loader no longer keeps its own window advanced "in
  lockstep" — like the conversation loader, it now follows the gift-wrap
  window's `windowSince` and re-requests via `reload()`. Removes its window,
  loadMore, loadEverything and exhausted; the rooms screen drives
  giftWraps.loadMore() + nip04.reload() and reads giftWraps.exhausted alone.

- Shared listener: extract WindowLoadTracker.trackingListener(forward) — the
  one place that feeds onActivity/onRelayResponded — replacing three copies of
  subscription-listener boilerplate and two newEose overrides.

- Drop the cold-boot instrumentation (bootStartMs/bootEventCount/bootEoseLogged
  + verbose per-call logs) from the gift-wrap manager; it was development
  scaffolding. (The debug-gated DmRelayDiagnosticsLogger stays.)

- Renames for clarity: DMsFromUserFilterSubAssembler -> ChatroomListNip04SubAssembler,
  ChatroomFilterSubAssembler -> ChatroomNip04SubAssembler, field nip04Dms -> nip04.

Behavior is unchanged: same auto-fill/prefetch, same all-relays-or-idle gating,
same gap-free conversation reveal. ~250 fewer lines and no more lockstep concept.
2026-06-01 14:12:02 +00:00
Claude 46a8d1d490 feat: gap-free conversation timeline (display floor across NIP-04 + NIP-17)
A thread renders the LocalCache union as events land, and NIP-04 (one
decrypt) paints faster than NIP-17 (gift-wrap unwrap = two NIP-44 decrypts).
So even with both windows requested to the same depth, a thread could
transiently show kind:4 messages with the kind:1059 messages that belong
between them still missing — and a user could read it as complete.

Introduce a per-conversation display floor: only reveal messages at or newer
than the deepest gift-wrap floor at which BOTH protocols have finished
loading, plus a 2-day margin (NIP-17 randomizes the gift wrap's outer
created_at up to 2 days, and relays filter on that outer time, so fetching
outer >= F only guarantees holding every inner time >= F+2d). The floor is
monotonic (revealed history never retracts) and a "loading older" boundary
shows at the oldest end until the window is exhausted, so incompleteness is
always visible rather than mistaken for "done".

- ChatroomFilterSubAssembler gains a WindowLoadTracker (loadingMore) and a
  reload(), so the conversation knows when NIP-04 has covered the floor; the
  scroll widen now gates on both protocols and calls reload() instead of a
  bare invalidate.
- ChatFeedView gains opt-in oldestVisibleTime (clip) + loadingOlder
  (boundary) params; public-chat / channel callers default to no-op.
- ChatroomView computes the floor from both loaders' idle state + windowSince
  and passes it down.

Honest limits: a relay that withholds data can't be conjured (we never hide
incompleteness, floor only descends); a sender backdating the gift-wrap outer
timestamp beyond the 2-day spec can still plant a late message, defended only
by "Load entire history". The rooms list is intentionally not clipped this
way — there, hiding a known conversation is worse than a row reordering.
2026-06-01 13:57:26 +00:00
Vitor PamplonaandGitHub 877d401e34 Merge pull request #3120 from vitorpamplona/claude/laughing-noether-2EmMZ
Move HTML parsing and broadcast service to commons for KMP
2026-06-01 08:12:55 -04:00
Vitor PamplonaandGitHub 51503f278d Merge pull request #3119 from vitorpamplona/dependabot/github_actions/actions-bfa3075405
chore(actions): bump the actions group with 2 updates
2026-06-01 08:11:42 -04:00
nrobi144andClaude Opus 4.6 1b17ce6975 fix(desktop): wire like and zap on comment items
- Fix like: read replyNote.event inside lambda (not captured val)
  to avoid stale null reference. Consume reaction into local cache.
- Wire zap on comments: uses zapNote (now internal) with 21 sats default
  via NWC connection, same flow as main action row
- Wire like/zap in both FeedScreen (inline expansion) and ThreadScreen

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-06-01 12:20:01 +03:00
nrobi144andClaude Opus 4.6 4b021351d3 fix(desktop): wire comment reactions + fix related content click navigation
- Wire onLike on CommentItem: ReactionAction.reactTo + broadcast
- Related content clicks use overlay navigation (ThreadScreen) since
  related notes may not be in the feed LazyColumn
- Add onNavigateToThreadOverlay param to ExpandedNoteContent
- Zap from comments deferred (requires full NWC flow)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-06-01 12:13:33 +03:00
nrobi144andClaude Opus 4.6 3f862637d1 fix(desktop): load comment author metadata on inline expansion
- Observe note.flow().replies so replyNotes recomputes when replies arrive
- Use loadMetadataBatched with explicit author pubkeys from reply events
- DisposableEffect for proper flow cleanup

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-06-01 12:09:28 +03:00
nrobi144andClaude Opus 4.6 08c7b5f214 fix(desktop): remove auto-scroll on card expansion
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-06-01 12:08:35 +03:00
nrobi144andClaude Opus 4.6 4fddfef5dd feat(desktop): inline card expansion in feed
- Add expandedNoteId state to FeedScreen — clicking a card expands it
  in-place instead of navigating to separate ThreadScreen
- AnimatedVisibility(expandVertically + fadeIn) for smooth expansion
- ExpandedNoteContent composable renders CommentsCard + RelatedContentSection
  below the expanded card within the same LazyColumn item
- Auto-scroll expanded card to top of viewport
- Thread reply subscriptions start on expand, cancel on collapse
- Only one card expanded at a time — clicking another collapses current
- Search bar stays visible (floating header above LazyColumn)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-06-01 12:03:16 +03:00
nrobi144andClaude Opus 4.6 92e210a584 fix(desktop): follow pill visibility, metadata loading, reply + view all wiring
- Fix follow pill layout: author row uses weight(1f) so pill has room
  (was invisible due to SpaceBetween squeezing)
- Fix comment metadata: observe metadataState so author info recomposes
  when kind:0 arrives from relay
- Wire "View all" on related content to navigate to author profile
- Wire reply button on CommentItem to open reply compose dialog

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-06-01 11:45:32 +03:00
nrobi144andClaude Opus 4.6 5aa2f519e7 feat(desktop): visual overhaul of thread detail view matching Layers design
- Create CommentsCard: OutlinedCard with "Comments N" header + badge,
  "Most recent" label, reply input slot, comment items slot
- Create CommentItem: lightweight comment row with avatar, name, handle,
  time, content, Reply/Like/Zap actions (replaces heavy FeedNoteCard for replies)
- Restyle InlineReplyInput: cyan "Send" pill button instead of plain icon
- Revise RelatedContentRow: image-overlay cards (200x140dp) with AsyncImage
  background, dark gradient overlay, white title + author + zaps
- Restructure ThreadScreen: root note card → CommentsCard → Related section

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-06-01 11:34:18 +03:00
nrobi144andClaude Opus 4.6 9194dac8f9 feat(desktop): related content section in thread view
- Create CompactNoteData @Immutable data class in commons for reuse
- Create RelatedContentSection composable with horizontal LazyRow
- Scan LocalCache for hashtag-matching + same-author notes
- Compact cards (160dp) with title, author, zap count
- Wire into ThreadScreen below reply notes
- Hidden when no related content found
- Subscriptions cancel on dispose

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-06-01 11:07:31 +03:00
nrobi144andClaude Opus 4.6 25c9cf4611 feat(desktop): share menu with copy/broadcast options
- Create ShareMenu composable with ShareMenuState
- 6 share options: Copy Text, Copy Note ID, Copy Event Link, Copy Raw JSON,
  Copy Web Link (njump.me), Broadcast
- Replace MoreVert overflow menu with Share icon + ShareMenu
- Use existing copyToClipboard helper for clipboard operations

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-06-01 11:07:19 +03:00
nrobi144andClaude Opus 4.6 cc1330adb6 feat(desktop): inline reply in thread view
- Create InlineReplyInput composable (avatar + TextField + Send button)
- SendState sealed interface (Idle/Sending/Error)
- Ctrl/Cmd+Enter keyboard shortcut to send
- Build kind:1 reply with NIP-10 e-tag + p-tag
- Optimistic display via localCache.consume + broadcastToAll
- Error shown inline with text preserved for retry
- Hidden for logged-out users

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-06-01 11:06:50 +03:00
nrobi144andClaude Opus 4.6 1294283937 feat(desktop): follow pill in feed card header
- Add headerTrailingContent slot to NoteCard for follow pill placement
- Add FollowPill composable (FilterChip with PersonAdd icon)
- Wire follow action in FeedScreen: FollowAction.follow + broadcastToAll
- Mutex guards concurrent follows to prevent kind:3 overwrites
- Expose lastContactListEvent on DesktopLocalCache for follow operations
- Hidden for own notes, already-followed users, and logged-out users

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-06-01 10:31:53 +03:00
nrobi144andClaude Opus 4.6 fd6e37b84a feat(desktop): slide-animated inline navigation with 2-level back stack cap
- Refactor ColumnNavigationState to use mutableStateListOf with direction tracking
- Add pushWithCap(maxDepth=2) — replaces top entry when cap reached
- Replace instant Surface overlay with AnimatedContent slide transitions (200ms)
- Add Esc key handler (onPreviewKeyEvent) for back navigation
- Add FocusRequester for keyboard nav to work after slide
- Apply to both DeckColumnContainer and SinglePaneLayout

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-06-01 10:23:24 +03:00
nrobi144andClaude Opus 4.6 144b911867 feat(desktop): move actions inside card + fix sidebar double active state
- Add bottomContent slot to NoteCard for actions to render inside card boundary
- Move NoteActionsRow into the slot in FeedNoteCard (both regular and repost paths)
- Add muted parameter to SidebarNavItem; mute Home when feed tabs are visible
- Resolves feedback: actions clearly belong to their card, sidebar doesn't
  compete with feed tab active state

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-06-01 10:17:08 +03:00
dependabot[bot]andGitHub 92989566cd chore(actions): bump the actions group with 2 updates
Bumps the actions group with 2 updates: [gradle/actions](https://github.com/gradle/actions) and [actions/cache](https://github.com/actions/cache).


Updates `gradle/actions` from 4 to 6
- [Release notes](https://github.com/gradle/actions/releases)
- [Commits](https://github.com/gradle/actions/compare/v4...v6)

Updates `actions/cache` from 4 to 5
- [Release notes](https://github.com/actions/cache/releases)
- [Changelog](https://github.com/actions/cache/blob/main/RELEASES.md)
- [Commits](https://github.com/actions/cache/compare/v4...v5)

---
updated-dependencies:
- dependency-name: gradle/actions
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: actions
- dependency-name: actions/cache
  dependency-version: '5'
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: actions
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-06-01 01:34:23 +00:00
m 9afa4a120f docs(namecoin): refresh design doc to match current main
- Update DEFAULT_ELECTRUMX_SERVERS table to the 6 clearnet entries
  actually shipped (testls.space + nmc2.bitcoins.sk + IP peer +
  relay.testls.bit + IP peer + electrum.nmc.ethicnology.com); remove
  ulrichard.ch and nmc2.lelux.fi which were retired.
- Update TOR_ELECTRUMX_SERVERS table to the current 7 entries
  including the relay.testls.bit hidden service on port 50001.
- Document the Namecoin Core RPC backend (NamecoinCoreRpcClient,
  NamecoinCoreRpcConfig, StartOS / umbrel / onion URL paths).
- Document CompositeNamecoinBackend + NamecoinFallbackPolicy chain
  (primary -> custom ElectrumX -> default ElectrumX) and the
  short-circuit-on-NameNotFound rule.
- Document the ifa-0001 `import` resolver (NamecoinImportResolver),
  including the bare-string / array short-hand forms and the
  default depth-4 / cycle-safe recursion.
- Document TOFU cert pinning (PINNED_ELECTRUMX_CERTS plus the
  user-supplied PEM store on Android and Desktop) and the Test
  Connection diagnostic returning ServerTestResult.
- Document name expiry enforcement on both backends and the
  NamecoinResolveOutcome sealed type used by resolveDetailed().
- Add a Commons module section covering NamecoinSettings (the shared
  serializable config) and NamecoinResolveState (UI state model).
- Add a Desktop section covering DesktopNamecoinNameService,
  DesktopNamecoinPreferences, LocalNamecoin lazy init, and the
  desktop NamecoinSettingsSection.
- Rename trustAllCerts -> usePinnedTrustStore throughout the doc,
  matching the rename in code.
- Update the Quartz paths from `nip05/namecoin/` to
  `nip05DnsIdentifiers/namecoin/` (the actual package layout) and
  refresh the file list to match what is on main today.
- Refresh the testing section: add backend-picker, on-chain zap,
  and NameNotFound cases; update tcpdump port set to cover 50001 /
  57002 / 8336; replace stale single IP example with reference to
  DEFAULT_ELECTRUMX_SERVERS.
- Architecture diagram now shows CompositeNamecoinBackend,
  NamecoinImportResolver, and NamecoinCoreRpcClient alongside
  ElectrumXClient.

No code changes.
2026-06-01 08:26:07 +10:00
David KasparandGitHub cc7731b59c Merge pull request #3117 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2026-05-31 21:33:17 +02:00
Crowdin Bot 77ea119b8b New Crowdin translations by GitHub Action 2026-05-31 11:31:27 +00:00
Vitor PamplonaandGitHub 7511e31d83 Merge pull request #3116 from davotoula/fix/versionname-worktree-cwd
Run git from rootDir in versionName detection so worktrees work
2026-05-31 07:29:46 -04:00
David KasparandGitHub 16beed71e5 Merge branch 'main' into fix/versionname-worktree-cwd 2026-05-31 12:03:26 +02:00
David KasparandGitHub 73bc81a6ba Merge pull request #3113 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2026-05-31 09:36:33 +02:00
Crowdin Bot 5adab91a78 New Crowdin translations by GitHub Action 2026-05-31 07:32:01 +00:00
davotoulaandClaude Opus 4.8 e54086d851 docs(skill): drop sync-timestamp filter from find-missing-translations
The Step 2.5 git "sync-timestamp" heuristic (skip keys added before the
last Crowdin export commit) produced false negatives: a key added shortly
before an export that translators hadn't reached yet is genuinely missing,
but the filter classified it as "Crowdin already decided" and dropped real
work. Replace it with the raw on-disk diff reconciled against the Crowdin
web UI's untranslated count; source-identical entries are skipped by
inspection instead.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-31 09:30:16 +02:00
davotoulaandClaude Opus 4.8 62a8841a18 i18n: translate Cashu wallet, top-up/nutzap, hashtag-label, podcast & music strings (cs, de, sv, pt-BR)
Catches up four locales on the missing strings/plurals for the new
Cashu (NIP-60) wallet, mint top-up/reload, NIP-61 nutzaps, NIP-32
hashtag labels, podcasts, and (pt-BR) music tracks/playlists & NIP-82
software releases. pt-BR was furthest behind (199 keys); cs/de/sv each
add 123 strings + 5 plurals. Czech plurals carry the full one/few/many/
other CLDR set. Notification-channel id calendar_reminder_channel_id
left untranslated by design.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-31 09:26:33 +02:00
Vitor PamplonaandGitHub 14d7741eb6 Merge pull request #3115 from vitorpamplona/claude/gallant-planck-cL2Kq
feat(cashu): show per-mint balance, drop inline recommend button
2026-05-30 20:00:55 -04:00
Claude 40a6465f3c fix: base rooms-list window paging only on private chats
The Messages list mixes private DMs (windowed) with public, ephemeral and
marmot-group rooms, which are membership-based — every room you're in shows
regardless of age, loaded by their own always-on loaders, not time-windowed.
The auto-fill was using whole-list geometry (lastVisible >= total/2), so an
old public chat at the bottom either stalled private paging (it inflated the
item count) or, with an oldest-item rule, would have dragged the private
window back years.

Now the widen trigger ignores non-private rows: it fires as the user
approaches the oldest LOADED private chat (event is ChatroomKeyable) within a
small prefetch margin, or when no private chat is loaded yet. The loading
spinner / "Load entire history" footer moves to that private boundary —
between the last loaded private chat and the older public rooms below it —
instead of sitting at the absolute bottom under unrelated old channels.

Windowing all chat types together was considered but rejected: it would hide
followed-but-inactive public channels, which must always appear.
2026-05-30 23:34:30 +00:00
Claude f26c00add0 refactor(commons): make HtmlParser KMP — drop java Charset dependency
Follow-up to the link-preview move: HtmlParser + HtmlCharsetParser were
stuck in jvmAndroid only because they spoke java.nio.charset.Charset.
There is no common Charset type in the Kotlin stdlib, so this reshapes
the API to speak IANA charset *names* (String) and pushes the single
genuinely-platform operation — byte->String decode — behind expect/actual.

- Move HtmlParser + HtmlCharsetParser to commonMain. Charset detection
  (meta-tag sniff + BOM sniff) is pure string/byte work; BOM detection no
  longer needs okio (manual leading-byte compare).
- Add `expect fun decodeBytes(bytes, charsetName)`:
    * jvmAndroid actual -> java.nio.charset (all JRE charsets, UTF-8 fallback)
    * iosMain actual -> NSStringEncoding for the common web charsets
      (UTF-8/16/32, Latin-1, CP1252, ASCII), UTF-8 fallback for the rest.
- UrlPreview (stays jvmAndroid; needs OkHttp) now reads response.body.bytes()
  and passes mimeType.charset()?.name().

Verified: commons compiles for JVM AND iosSimulatorArm64, verifyKmpPurity
passes, commons jvmTest passes, amethyst play + fdroid compile.
2026-05-30 23:34:05 +00:00
Claude fd7ac921d1 Merge remote-tracking branch 'origin/main' into claude/gallant-planck-cL2Kq 2026-05-30 23:27:19 +00:00
Claude 94dc759f8a feat(cashu): add standalone mint top-up screen
Add a dedicated TopUpMintScreen + TopUpMintViewModel for funding a
specific mint outside the zap flow. Each mint row on the wallet screen
gets an add-funds icon that opens the screen with that mint as the fixed
target.

The screen reuses the same funding primitives as the zap-driven Reload
screen — CashuWalletState.rebalance for a mint-to-mint move and
startMintFromLightning/completeMintFromLightning for an LN top-up (NWC
or external invoice) — but drops all zap machinery: no recipient, no
shared-mint intersection, no fixed send amount/shortfall, no terminal
nutzap, no fund-then-send atomicity. This keeps the double-spend-
sensitive zap pipeline untouched.

Shares SectionHeader/SourceRow/shortMint/sats from ReloadMintScreen
(widened to internal) instead of duplicating them.
2026-05-30 22:59:08 +00:00
Claude 58ad87c900 refactor(commons): move link-preview fetcher to commons
Third slice of the amethyst→commons migration. UrlPreview (OpenGraph
link-preview fetcher) and HtmlParser already wrapped the extracted
commons preview parsers (MetaTagsParser/OpenGraphParser/HtmlCharsetParser);
this consolidates the whole link-preview concern in commons.

- Move service/previews/{UrlPreview,HtmlParser} into commons jvmAndroid
  service preview package. They land in jvmAndroid (not commonMain)
  because UrlPreview uses OkHttp and HtmlParser uses java.nio.charset —
  both JVM-only. No Android-framework or keystone coupling: the caller
  injects the OkHttpClient as a lambda.
- Add explicit okhttp + okhttp-coroutines deps to commons jvmAndroid
  (previously only present transitively via coil-okhttp).
- Re-point the single caller (model/UrlCachedPreviewer).

commons JVM compile + verifyKmpPurity pass; amethyst play + fdroid compile.
2026-05-30 22:57:37 +00:00
Claude cad22689d1 feat: window NIP-04 with NIP-17 in lockstep in the conversation screen
Previously a conversation loaded its NIP-04 (kind:4) history in full while
NIP-17 was windowed, so a thread could reach deeper on one protocol than the
other. Now both follow a single floor: the account-wide gift-wrap window.

- AccountGiftWrapsEoseManager exposes windowSince(user) — the current window
  floor.
- The conversation NIP-04 loader (ChatroomFilterSubAssembler / filterNip04DMs)
  requests kind:4 from that same floor instead of the EOSE cursor, so it never
  reaches further back than NIP-17. The gift-wrap manager is plumbed in via
  ChatroomFilterAssembler from RelaySubscriptionsCoordinator.
- The conversation scroll handler now advances both: it widens the gift-wrap
  window (NIP-17) and re-invalidates the chatroom sub so NIP-04 re-requests at
  the new, wider floor. Gated by the gift-wrap loadingMore so it steps once at
  a time, stopping at exhaustion.

Because the floor is shared (not an independent per-room window), the two
protocols stay aligned even when the rooms list has already widened the
window. Display still reads from LocalCache, so any messages already cached
(e.g. from a prior full load) keep showing regardless of the request floor.
2026-05-30 22:57:18 +00:00
Claude 5f1514a152 refactor(commons): move relay broadcast tracker to commons
Second slice of the amethyst→commons migration. BroadcastTracker +
BroadcastEvent/RelayResult/BroadcastStatus are platform-agnostic relay
event-broadcast logic (no keystone coupling, no Android) that Desktop and
the CLI can reuse.

- Move service/broadcast/{BroadcastModels,BroadcastTracker} into
  commons commonMain service/broadcast.
- Replace the two commonMain purity-gate violations:
  System.currentTimeMillis() -> TimeUtils.now() (startedAt is only used
  to sort the active-broadcast list) and java.util.UUID.randomUUID() ->
  RandomInstance.randomChars(16) for the tracking id.
- Re-point the 4 Android callers (AccountViewModel + broadcast UI).

verifyKmpPurity passes; amethyst play + fdroid both compile.
2026-05-30 22:42:05 +00:00
Claude 43de099e7e feat: scroll-driven NIP-17 loading in conversations (drop eager load-all)
Replace the eager full gift-wrap load on conversation open with the same
scroll-driven widening the rooms list uses. As the user scrolls a thread
toward older messages (reverse-laid-out, so older = higher indices), the
account-wide gift-wrap window widens one step at a time — prefetching at the
midpoint so older messages land before the top is reached — and stops once
the window is exhausted. A thread that already fills the viewport doesn't
load anything extra until you actually scroll back.

The shared chat feed (used by public channels, ephemeral chats, live
activities, marmot groups too) stays generic: it gains an opt-in
listStateObserver slot, and only the private-DM screen attaches the
gift-wrap loader through it. NIP-04 in a conversation is still loaded in
full (it was already, and a single room's kind:4 is cheap), so only the
windowed NIP-17 side is scroll-driven; the thread is time-sorted so the two
merge without reordering. loadEverything stays for the rooms-list button.
2026-05-30 22:41:08 +00:00
Vitor PamplonaandGitHub fc6d157cbc Merge pull request #3114 from vitorpamplona/claude/brave-franklin-4taet
Remove wallet reordering UI and drag-and-drop functionality
2026-05-30 18:38:10 -04:00
Claude dddbb45680 feat(cashu): show per-mint balance, drop inline recommend button
Display spendable sats per mint in the wallet screen's Mint section and
remove the thumbs-up recommend button from each mint row — mint
recommendations are managed from Cashu Wallet Settings.

- Add reactive CashuWalletState.mintBalances (StateFlow<Map<String, Long>>)
  derived from token entries, mirroring balanceSats.
- Expose it through CashuWalletViewModel and render the per-mint total in
  MintRow using the existing wallet_sats label.
- Remove the per-row recommend (ThumbUp) action and its wiring;
  recommendMint() stays for the Settings screen.
2026-05-30 22:35:37 +00:00
Claude 58973701d0 feat: load full gift-wrap history when a conversation opens
The conversation screen issues its own unbounded NIP-04 REQ (full room
history) but has no NIP-17 fetch of its own — it relies on the account-wide
gift-wrap loader, which is windowed and only driven by the rooms list. So a
thread could show a deep NIP-04 history but only the NIP-17 messages inside
the current (possibly 7-day) window, silently hiding older gift-wrapped
messages.

Gift wraps are addressed to us, not the partner, so a relay can't filter
them per-room; the only lever is the shared account window. On opening a
conversation, ask the gift-wrap loader to pull everything (loadEverything).
It's idempotent via the isExhausted guard, so only the first conversation
opened in a session pays the cost; reopening threads is a no-op.
2026-05-30 22:30:26 +00:00
Claude a70e4cebc0 fix: tidy wallet screen UI
- 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
2026-05-30 22:24:28 +00:00
Claude e66c27e375 refactor(commons): move CLI-safe util extensions out of amethyst
First low-friction slice of the amethyst→commons migration
(commons/plans/2026-05-30-amethyst-to-commons-migration.md): the model
nipNN state holders are all blocked by the LocalCache/Note/Account
keystone (Phase A), so start with the genuinely Android-free utilities.

- Delete amethyst service/IterableExt.kt — exact duplicate of the existing
  commons util/IterableUtils.kt (Iterable.replace); re-point 4 callers.
- Move retryIfException (CoroutinesExt.kt) into commons util/CoroutinesUtils.kt.
- Move togglePresenceInSet (SetExt.kt) into commons util/SetUtils.kt.

All commonMain-safe (verifyKmpPurity passes). amethyst play + fdroid both
compile against the relocated helpers.
2026-05-30 22:23:47 +00:00
Claude 9ac7ee8026 fix: race conditions in the DM window loaders
Audit follow-up — close three concurrency holes exposed by the auto-fill
loop, which calls into the loaders from the UI thread while the bundled
invalidation runs updateFilter on Dispatchers.IO:

- windows map: was a plain HashMap mutated from both the UI thread
  (loadMore/loadEverything) and Dispatchers.IO (updateFilter). Concurrent
  getOrPut can corrupt the table. Switch to ConcurrentHashMap.computeIfAbsent.

- WindowLoadTracker watchdog: a stale watchdog waking from delay just as a
  new startLoading ran could complete the *new* window (flip loading=false
  and cancel the new watchdog), leaving it stuck. Guard each poll with a
  generation token so a superseded watchdog bows out.

- scope field: written on IO (newSub), read on the UI thread (loadMore);
  marked @Volatile for visibility.

- cold-boot diagnostic maps (bootStartMs/bootEventCount/bootEoseLogged) are
  written from the concurrent relay reader callbacks during the boot flood;
  switch to ConcurrentHashMap + an atomic merge so they can't corrupt or
  hang under that load.
2026-05-30 22:14:30 +00:00
Claude 6f9c5bbdf0 fix: complete DM windows on quiescence, add load-entire-history button
The fixed 15s window-load timeout fired mid-flood on accounts with a large
DM history: a relay streaming thousands of stored gift wraps never EOSE'd
within 15s, so the window was declared "loaded" while events were still
pouring in and before they were decrypted into rooms. The rooms list still
looked empty, so auto-fill widened again — re-issuing an ever-wider REQ that
re-downloaded the whole history, over and over, every 15s.

WindowLoadTracker now completes a window on activity quiescence instead of a
wall clock: it stays loading until every expected relay EOSEs, or the event
stream goes quiet for a few seconds. Every event (stored backfill included)
bumps the idle timer via onActivity, so a relay mid-flood is never mistaken
for a finished window; an absolute cap bounds pathological dribble. Both DM
loaders feed event activity in (the NIP-04 loader now uses a custom listener
so it sees stored events, not just live ones).

Also add a "Load entire history" button to the rooms-list footer: it jumps
the window straight to the max lookback (TimeWindowPagination.loadAll) so a
single REQ pulls everything — the pre-windowing behavior — and marks the
window exhausted so the auto-fill loop stops.
2026-05-30 22:05:57 +00:00
Vitor PamplonaandGitHub 4ae606805f Merge pull request #3112 from vitorpamplona/claude/gracious-cori-uLr4P
Move NIP-51/72 decryption caches and models to commons
2026-05-30 18:04:49 -04:00
Claude abc9cd14cf refactor: move InterestSet to commons
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
2026-05-30 21:59:26 +00:00
Claude a38388fce5 refactor: move LabeledBookmarkList to commons
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
2026-05-30 21:52:06 +00:00
Claude 593c320004 refactor: move Mute/People/Community decryption caches to commons
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
2026-05-30 21:46:13 +00:00
Claude d311a01964 feat: auto-fill and prefetch the rooms list with a growing DM window
Replace the fire-once scroll detector with a viewport-fill + prefetch loop
so the messages screen stays ahead of the user instead of stranding a
near-empty list.

One condition drives three behaviors: widen the DM time windows when the
feed is empty, or when the last visible row crosses the midpoint of what's
loaded. While the list is short everything is visible, so the midpoint is
always crossed and it keeps widening until the list overflows the viewport
with a buffer below the fold; once full it only fires again as the user
scrolls past the new midpoint, so a fresh chunk lands well before the end.
It stops only when the window is exhausted (reached the 10-year lookback —
nothing older exists), which also gives the empty-account case a real
terminating condition instead of the old runaway cascade.

- TimeWindowPagination: optional geometric step growth + a hard max-lookback
  floor with isExhausted(), so a sparse / single-person history converges in
  ~10 requests. Default stays linear/unbounded; existing callers unchanged.
- WindowLoadTracker: a window counts as loaded only once ALL of its relays
  have answered (EOSE / live event) or a timeout fires — not on the first
  EOSE. This stops a fast, near-empty relay from clearing the gate and
  letting the fill loop outrun the slow relay that holds the conversations.
- Both DM loaders (NIP-17 gift wraps + NIP-04) expose loadingMore (= window
  still loading) and exhausted, advance in lockstep, and gate each widen on
  the tracker. The rooms screen shows a spinner until history is exhausted
  rather than flashing the empty state while older windows are still in
  flight.
2026-05-30 21:44:26 +00:00
Claude ab72427445 refactor: move HashtagListDecryptionCache to commons
Pure quartz-only decryption cache — extract to
commons/model/nip51Lists/hashtagLists. Re-points Account,
FeedDecryptionCaches, and the sibling HashtagListState.

https://claude.ai/code/session_01JFbYZdVV4QmDC4eQYvEccb
2026-05-30 21:43:55 +00:00