Commit Graph
14401 Commits
Author SHA1 Message Date
Claude ce32dc4fc3 feat(zap): Reload Mint screen to fund a recipient's mint, then nutzap
When the cashu logo on an amount chip is NEEDS_RELOAD (you hold enough cashu,
but not in the recipient's mint), it now shows a dimmed logo with a "+" badge
that opens a full Reload Mint screen instead of silently falling back to
Lightning.

The screen tells the whole story and auto-sends the nutzap on success:
- Destination ("mint to reload") with its shortfall; selectable when the
  recipient accepts several mints we hold.
- All cashu balances for context.
- Source ("funds from"): another mint (mint-to-mint rebalance, no new sats) or
  Lightning. Mint rows are disabled when they can't cover shortfall + fee.

Funding paths:
- Rebalance: CashuWalletState.rebalance moves cashu between mints.
- Lightning: mint a fresh quote at the destination, auto-pay via NWC when a
  wallet is configured (else show the invoice to copy), poll, then issue.

Wiring: new Route.ReloadMint + AppNavigation entry; a ReloadMintRequest handed
off via an LruCache on AccountViewModel (mirrors the manual-zap pattern);
onReloadNutzap threaded through the zap popup overloads and the chip; callers
(ReusableZapButton, ReactionsRow, NestActionBar) navigate to the screen.

IMPOSSIBLE (not enough cashu anywhere) keeps falling back to Lightning — the
reload screen is only for moving existing cashu.

https://claude.ai/code/session_01HNE2z7CSYZ2G8KwC5fziJn
2026-05-29 19:32:30 +00:00
Claude c73e7924dd feat(cashu): add mint-balance queries and mint-to-mint rebalance orchestration
Foundation for the Reload Mint screen (NEEDS_RELOAD funding path):

- peekMintBalances(): per-mint spendable balance from in-memory proofs
  (synchronous; feeds the balances list and source-feasibility checks).
- recipientSharedMints(): the recipient's accepted mints we also hold —
  the candidate reload destinations.
- rebalance(source, target, sats): mints a fresh quote at the target and
  pays its invoice by melting at the source, moving cashu between mints with
  no new sats. Verifies the source covers amount + fee before spending,
  abandons the unpaid quote otherwise, and leaves a resumable kind:7374 if the
  target hasn't confirmed yet — money is never lost on failure.

https://claude.ai/code/session_01HNE2z7CSYZ2G8KwC5fziJn
2026-05-29 19:19:33 +00:00
Claude 6f7cacf84b refactor(zap): merge on-chain amount presets into the single zap-amount list
The on-chain rail no longer has its own editable preset list. There is now
one zap-amount list used by every rail; on-chain simply filters it by the
shared MIN_ONCHAIN_ZAP_SATS floor (1000 sat), and the unified chip already
gates each logo per amount.

- Settings: drop the public onchainZapAmountChoices StateFlow and its editor
  section in the Update Zap Amount dialog. The serialized field is kept for
  backward/cross-client (NIP-78) compatibility and migration: on load the two
  saved lists are unioned into zapAmountChoices (mergeZapAmounts), and on save
  the on-chain-eligible subset is written back so older clients still get
  sensible on-chain presets. The round-trip is idempotent.
- updateZapAmounts / changeOnchainZapAmounts lose the separate on-chain
  parameter throughout (Account, AccountViewModel, UpdateZapAmountViewModel).
- OnchainZapSendDialog draws its quick presets from the single list filtered
  by the minimum.
- MIN_ONCHAIN_ZAP_SATS is now a single shared constant in the model, replacing
  the copies previously private to OnchainZapSendDialog and ReactionsRow.
- Remove the now-unused on-chain amount strings; clarify the zap-amounts
  explainer to note amounts apply to all rails.

Adds ZapAmountMergeTest covering the union/dedup/idempotent-round-trip.

https://claude.ai/code/session_01HNE2z7CSYZ2G8KwC5fziJn
2026-05-29 18:28:48 +00:00
Claude c1c1a69148 feat(zap): unify zap picker into one chip per amount with per-rail logos
Replace the three parallel chip rows (cashu / lightning / on-chain) in the
zap amount popup with a single pill per amount that shows a tappable logo
for each rail that can actually pay it. Tapping the amount fires the
cheapest/fastest available rail (cashu funded -> lightning -> on-chain);
tapping a specific logo forces that rail.

Rail gating:
- Cashu: precise per-amount status (FUNDED / NEEDS_RELOAD / IMPOSSIBLE /
  UNAVAILABLE) computed from in-memory proofs — the one balance that is free
  to read synchronously. Only FUNDED is offered for now.
- Lightning: optimistic — an external wallet can pay any invoice, so it's
  offered whenever the recipient can receive, with no sender-balance fetch.
- On-chain: offered when a backend is configured and the amount clears the
  on-chain minimum; UTXO sufficiency stays a send-time check.

Also fixes the original "No proofs available at <mint>" failure: nutzap mint
selection now picks the shared mint where we hold the most balance instead of
the first one the recipient lists (peekNutzapFunding), so a zap no longer
fails when funds sit in a different shared mint. Cashu capability is now
resolved against the note author (who sendNutzap actually pays) rather than
split recipients.

Amount presets from the (still separate) lightning and on-chain settings are
merged into one sorted set for display.

https://claude.ai/code/session_01HNE2z7CSYZ2G8KwC5fziJn
2026-05-29 18:09:22 +00:00
Vitor PamplonaandGitHub c3b40c4369 Merge pull request #3075 from vitorpamplona/claude/cashu-wallet-amethyst-sdOWe
Add NIP-60 Cashu wallet and NIP-61 nutzap support
2026-05-29 12:59:20 -04:00
Claude 4e18906f9a feat(nutzap): progress bar during send, matching lightning UX
The cashu zap chip previously fired its async send and closed the
popup with no visible motion until the kind:9321 round-tripped
back (1-2 seconds of "did the tap register?" silence). Wire the
same zappingProgress channel the lightning path uses so the bar
visibly animates from tap to completion.

Pieces:

- CashuWalletOps.sendNutzap: optional `onProgress: (Float) -> Unit`
  emitting at the network checkpoints — 0.55 after the swap (the
  long mint round-trip), 0.80 after the kind:9321 publish (the
  recipient can see it), 0.95 after keep + delete, 1.0 after the
  kind:7376 history.

- CashuWalletState.sendNutzap: forwards onProgress to ops and
  also emits 0.20 after scrubLocallyStaleProofs so the bar moves
  before the slow swap call begins.

- AccountViewModel.sendNutzap: accepts an `onProgress: (Float) -> Unit`
  parameter (default no-op so existing callers compile unchanged)
  and threads it through to CashuWalletState.

- ReactionsRow.onNutzap callback: emits 0.05 immediately on tap
  for instant click feedback, then passes the same `onProgress`
  channel the lightning chip uses so zappingProgress drives the
  same progress indicator. The existing onError reset already
  zeroes the bar on failure.

Resulting cadence at the UI:
  tap → 0.05 (instant)
       → 0.20 (scrub done)
       → 0.55 (mint swap done)
       → 0.80 (kind:9321 out)
       → 0.95 (token rollover + NIP-09 out)
       → 1.00 (history out, bar hides).
2026-05-29 15:32:25 +00:00
Claude 30a845a6c1 Merge remote-tracking branch 'origin/main' into claude/cashu-wallet-amethyst-sdOWe
# Conflicts:
#	amethyst/src/main/java/com/vitorpamplona/amethyst/model/LocalCache.kt
2026-05-29 13:32:49 +00:00
Vitor PamplonaandGitHub c22cd0d242 Merge pull request #3086 from carpedkm/bounty-1497-live-chat-broadcast
Fix live stream chat relay fallback
2026-05-29 09:18:38 -04:00
Vitor PamplonaandGitHub a49f57505a Merge pull request #3102 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2026-05-29 09:15:37 -04:00
Crowdin Bot b1f0f04bcc New Crowdin translations by GitHub Action 2026-05-29 13:02:55 +00:00
Vitor Pamplona c1dd59a068 Merge branch 'main' of https://github.com/vitorpamplona/amethyst
# Conflicts:
#	commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/relayClient/subscriptions/LifecycleAwareKeyDataSourceSubscription.kt
2026-05-29 08:57:31 -04:00
Vitor Pamplona c1c4f7f72a Uses the parent note's p-tags to load children notes that cannot be found 2026-05-29 08:47:28 -04:00
Vitor Pamplona 90109c9095 Removes the need of idHex on the BlankNote 2026-05-29 08:40:31 -04:00
Vitor Pamplona ca8589b29c Quick check to make sure the gatherers are not duplicated 2026-05-29 08:37:08 -04:00
Vitor Pamplona 350aadc578 Pushes event hints for citations in Public Messages 2026-05-29 08:36:41 -04:00
Vitor PamplonaandGitHub fd88e2f8a5 Merge pull request #3095 from vitorpamplona/claude/amazing-ptolemy-26Nek
Use locale-aware date/time formatting throughout the app
2026-05-29 08:30:41 -04:00
Vitor PamplonaandGitHub 9a6400a6e3 Merge pull request #3103 from greenart7c3/claude/elegant-knuth-grbfU
Fix lifecycle-aware subscription grace timer starvation
2026-05-29 06:34:53 -04:00
Claude 5667bd53c3 fix(relays): keep lifecycle-aware grace timer running while backgrounded
The grace-period unsubscribe in LifecycleAwareKeyDataSourceSubscription ran
on the composition scope from rememberCoroutineScope(), whose dispatcher is
coupled to the UI frame clock. When the app is backgrounded the frame clock
stops ticking, so the pending unsubscribe could be starved and never fire.
Because closing the REQ is what drives the relay disconnect (via desiredRelays
-> RelayPool.updatePool), the connection could linger indefinitely. This is
most visible on the relay feed, whose dedicated one-off relay is kept alive by
nothing else.

Drive the grace timer from Lifecycle.currentStateFlow on a dedicated
Dispatchers.Default scope instead. collectLatest cancels the pending delay
automatically when the lifecycle returns to STARTED, preserving the 30s
app-switch grace while ensuring the timer fires reliably in the background.

https://claude.ai/code/session_01SesftJphLwvLtn1fJB5zx8
2026-05-29 09:29:48 +00:00
Vitor PamplonaandGitHub 8fcfbf8477 Merge pull request #3098 from nrobi144/feat/desktop-visual-personality
feat(desktop): Visual Personality Overhaul — Unified Theme, Sidebar, Search, Cards
2026-05-29 05:13:00 -04:00
nrobi144andClaude Opus 4.6 dd5b61a6f0 feat(desktop): add "All Screens" item before feeds in sidebar
Opens the App Drawer (same as Cmd+K) for quick access to all
available screen types. Positioned between main nav and FEEDS section.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-29 07:09:50 +03:00
nrobi144andClaude Opus 4.6 71a38674b0 fix(desktop): first feed item 16dp top padding via LazyColumn contentPadding
- Revert spacer change (back to 60dp)
- Add top = 16.dp to LazyColumn contentPadding so first card has
  extra spacing and slides nicely under the floating search header

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-29 07:02:53 +03:00
nrobi144andClaude Opus 4.6 cc698c5e91 fix(desktop): sidebar starts expanded + extra 16dp feed margin
- Sidebar always starts expanded (ignores persisted collapsed state)
- Feed spacer 60dp → 76dp (extra 16dp gap between header card and first item)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-29 07:02:52 +03:00
nrobi144andClaude Opus 4.6 0e431d674c feat(desktop): branded loading screen instead of login flash on startup
- Add AccountState.Loading as initial state (was LoggedOut)
- Show centered "Amethyst" + spinner while accounts load from storage
- After loadSavedAccount(): transition to LoggedIn or LoggedOut
- No more 0.5s flash of login screen when account exists

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-29 07:02:52 +03:00
nrobi144andClaude Opus 4.6 da0aaaa281 fix(desktop): wider collapsed sidebar + filled Tor icon when connected
- COLLAPSED_WIDTH 56dp → 64dp so icons aren't truncated
- Tor connected: use Security icon (filled shield) instead of Shield
- Tor off/connecting/error: keep outlined Shield icon

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-29 07:02:52 +03:00
nrobi144andClaude Opus 4.6 12918e7045 feat(desktop): Tor/Bunker status as proper sidebar nav items
- Replace compact icon Row at bottom with SidebarNavItem-style items
- Tor: shows "Tor: Off/Connecting/Connected/Error" with Shield icon
- Bunker: shows "Bunker: OK" with Favorite icon (only when connected)
- Both use same shape, hover, and label pattern as other sidebar items
- Collapsed mode: icon-only with tooltip, same as nav items

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-29 07:02:52 +03:00
nrobi144andClaude Opus 4.6 7ff4d8e3b7 fix(desktop): avatar ripple + P3 hardcoded colors + inline shapes
Avatar/Account switcher:
- Rewrite SidebarAccountHeader with same shape/hover as other nav items
- Entire row (avatar + display name) is clickable with rounded clip
- Inline DropdownMenu replaces overlaid AccountSwitcherDropdown
- Collapsed: compact avatar with same rounded hover treatment

P3 #004 — Hardcoded status colors:
- Add StatusGreen/StatusRed/StatusAmber to commons Colors.kt
- Replace 32 inline Color() values across 10 files with theme tokens
- Color.Red → MaterialTheme.colorScheme.error where appropriate
- Color.Green/Gray → StatusGreen/onSurfaceVariant

P3 #005 — Inline shapes:
- RoundedCornerShape(8.dp) → MaterialTheme.shapes.small (~20 files)
- RoundedCornerShape(12.dp) → MaterialTheme.shapes.medium
- RoundedCornerShape(16.dp) → MaterialTheme.shapes.large
- Pill shapes (100dp/999dp) kept as-is

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-29 07:02:52 +03:00
nrobi144andClaude Opus 4.6 41a59a9499 fix(desktop): wire Open Full Search via LocalOpenFullSearch CompositionLocal
- Add LocalOpenFullSearch CompositionLocal (navigates to Search column)
- Provided at Main.kt level alongside LocalFeedSearchActive
- FeedScreen reads it directly — no param threading needed
- "Open full search" link in expanded header now actually opens Search column
- Removed unused onOpenSearch param from DeckColumnContainer

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-29 07:02:52 +03:00
nrobi144andClaude Opus 4.6 b8f6751a68 fix(desktop): address P2 todos — feed margin + history click
Todo #001: Feed content margin under search
- Animate spacer height: 60dp collapsed → 300dp expanded (tween 200ms)
- Feed items no longer hidden by expanded search card

Todo #002: History item click populates input
- SearchHistorySection gains onHistoryItemClick callback
- Clicking recent search populates searchText with query text
- LaunchedEffect(searchText.text) triggers updateFromText() → relay search

Todo #003: Cmd+F context (deferred — P2 stays open)
- Currently Cmd+F toggles inline search on feeds only
- On non-feed screens it's a no-op (user can click Search in sidebar)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-29 07:02:52 +03:00
nrobi144andClaude Opus 4.6 c7483a4f0d fix(desktop): SearchPill hover bounds + AccountSwitcher ripple size
- SearchPill: move hoverHighlight() inside Surface content Row so it's
  clipped to the pill's 36dp height (was drawing on parent Row height)
- AccountSwitcherDropdown: reduce IconButton from 48dp to 40dp so ripple
  circle fits within collapsed sidebar width

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-29 07:02:51 +03:00
nrobi144andClaude Opus 4.6 c53c8238fb fix(desktop): theming + modifier review fixes from multi-agent analysis
Shapes:
- DeckSidebar: RoundedCornerShape(8.dp) → MaterialTheme.shapes.small
- NoteCard images: RoundedCornerShape(8.dp) → MaterialTheme.shapes.small

Modifier patterns:
- NoteCard: remove hardcoded .fillMaxWidth() — callers now pass it
- FeedScreen NoteCard callers: add explicit Modifier.fillMaxWidth()

Ripple clipping (from prior commit):
- SearchPill: clip(pill shape) before hoverHighlight()
- Sidebar items: clip before clickable
- ColumnHeader: padding before pointerInput

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-29 07:02:51 +03:00
nrobi144andClaude Opus 4.6 55704d520b fix(desktop): ripple/hover clipping — correct modifier ordering
- SidebarNavItem/SidebarFeedItem: .clip().clickable().background()
  — ripple now clipped to RoundedCornerShape(8dp) bounds
- ColumnHeader: .padding() before .pointerInput() — gesture detection
  respects horizontal padding
- SearchPill: .clip(pill shape) before .hoverHighlight() — hover
  drawBehind rect clipped to pill shape, not parent rectangle

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-29 07:02:51 +03:00
nrobi144andClaude Opus 4.6 5e128cb535 fix(desktop): clear search scrim when navigating via sidebar
Sidebar onNavigate callback now sets searchActiveState.value = false,
clearing the feed search expansion and sidebar dim overlay when the
user clicks any nav item (Home, Messages, Settings, etc.)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-29 07:02:51 +03:00
nrobi144andClaude Opus 4.6 971d20be77 fix(desktop): revert relay debounce to 300ms, separate 1s history save
- Relay query debounce stays at 300ms (AdvancedSearchBarState default)
- Separate 1s debounce on searchText: when user stops typing for 1s,
  save the query to SearchHistoryStore (assumes intent confirmed)
- LaunchedEffect(searchText.text) auto-cancels on each keystroke,
  so only fires after 1s of inactivity
- No duplicates: addToHistory() deduplicates by serialized query

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-29 07:02:51 +03:00
nrobi144andClaude Opus 4.6 956eee67d7 feat(desktop): 1s debounce + auto-save search history on collapse
- Increase relay query debounce from 300ms to 1000ms for inline search
  (reduces unnecessary relay load while typing)
- Save query to SearchHistoryStore when search collapses (if non-empty)
- SearchHistoryStore.addToHistory() already deduplicates by serialized query

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-29 07:02:51 +03:00
nrobi144andClaude Opus 4.6 28e8fc627e feat(desktop): polished inline search states — loading, empty, streaming
- LinearProgressIndicator at top of expanded card (animated in/out)
- Loading state: centered icon + "Searching N relays..."
- Empty state: "No results found" / "No search relays configured"
- Results stream in incrementally from relays
- 1s debounce for relay queries, save to search history (no dupes)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-29 07:02:51 +03:00
nrobi144andClaude Opus 4.6 e9756d41aa fix(desktop): add loading indicator + relay diagnostics for inline search
- Show "Searching N relays..." with spinner while waiting for results
- Show "No search relays configured" if searchRelays is empty
- Observe isSearching, peopleResults, noteResults directly for state

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-29 07:02:51 +03:00
nrobi144andClaude Opus 4.6 e587bb946e feat(desktop): wire relay subscriptions for inline search results
FeedTabsHeader now creates actual NIP-50 relay subscriptions when
typing in the inline search, matching the full SearchScreen wiring:

- Collect debouncedQuery (300ms) from AdvancedSearchBarState
- Access searchRelays from LocalRelayCategories
- rememberSubscription for people search (MetadataEvent kind 0)
- rememberSubscription for note search (SearchFilterFactory filters)
- Results flow into SearchResultsList (reused from search/ package)
- 10s timeout for silent relays
- Subscriptions auto-cleanup on collapse via DisposableEffect

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-29 07:02:50 +03:00
nrobi144andClaude Opus 4.6 02679cc596 feat(desktop): animated search expand, tab-collapse, reuse SearchResultsList
1. AnimatedVisibility for search card expand/collapse (expandVertically
   + fadeIn 200ms, shrinkVertically + fadeOut 150ms)
2. Feed tab clicks now collapse the search (onSearchExpandedChange(false)
   before switching feed)
3. When typing in search: reuses SearchResultsList composable from
   search/ package with AdvancedSearchBarState (same as full SearchScreen)
   - Empty input shows search history (recent + saved)
   - Typing shows live people + note results grouped by kind
   - "Open full search" link at bottom for advanced filtering

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-29 07:02:50 +03:00
nrobi144andClaude Opus 4.6 bec8fd81ea fix(desktop): search card floats above scrim, sidebar dims separately
- Header card pulled OUT of ReadingColumn into outer Box layer 3
  (rendered after scrim, so it floats visually above it)
- Feed content in layer 1 with spacer for header height
- Scrim in layer 2 covers only feed content
- Sidebar gets its own scrim overlay via Box wrapper in Main.kt
- Search card stays sharp/visible while everything else dims

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-29 07:02:50 +03:00
nrobi144andClaude Opus 4.6 c8e9a4fa6a fix(desktop): search card extends downward, tabs always visible
- Feed tabs + compose button always visible (never hidden)
- Search pill becomes active input when focused, card extends downward
  with history section below the always-visible header row
- Scrim only covers feed content below, header stays sharp via zIndex(10f)
- Cmd+F focuses search, Escape or click-scrim dismisses

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-29 07:02:50 +03:00
nrobi144andClaude Opus 4.6 ec8fbd3980 feat(desktop): full-width search expansion with Cmd+F via CompositionLocal
- FeedTabsHeader expands to full-width search card when active
  - Feed tabs hidden, search input fills entire header width
  - History section (recent + saved) shown below input
  - "Open full search" link at bottom
- Scrim overlay dims feed content when search is active
- Cmd+F toggles search via LocalFeedSearchActive CompositionLocal
  - MutableState<Boolean> provided at Window level
  - FeedScreen reads it directly, no param threading needed
- SearchPill simplified back to clickable-only (no inline expansion)
- Escape or click scrim dismisses search

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-29 07:02:50 +03:00
nrobi144andClaude Opus 4.6 c69c4462b9 fix(desktop): search pill visibility, sidebar + Add Feed button
- SearchPill: fix collapsed state not rendering — removed nested
  Box/Surface/matchParentSize, replaced with simple if/else on
  expanded state within single Surface+Row
- Sidebar: add "+ Add Feed" item below custom feeds in FEEDS section
- Dropdown menu moves outside Surface to render properly

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-29 07:02:50 +03:00
nrobi144andClaude Opus 4.6 55e7fcb903 fix(desktop): feed header card design, inline search, compact tabs
- FeedTabsHeader wrapped in Surface card (border + rounded corners)
- Feed tabs compact with spacedBy(4dp), removed "+ More" (in sidebar)
- SearchPill centered with weight(1f), expands inline to BasicTextField
  with DropdownMenu showing recent + saved searches
- Removed Dialog-based SearchSpotlight (wrong UX — created separate AWT window)
- Cmd+F now navigates to Search column via navigateToScreen callback
- All feed tab types (Following/Global/Custom) properly handled in onClick

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-29 07:02:49 +03:00
nrobi144andClaude Opus 4.6 c21b85c83e feat(desktop): add search spotlight overlay with Cmd+F shortcut
- SearchSpotlight: global overlay with scrim, auto-focused input, recent/saved
  searches, hashtag quick-jump, and "Open full search" link
- SearchPill: compact reusable pill component with hover highlight and shortcut hint
- Cmd+F keyboard shortcut wired in MenuBar to open/close spotlight
- SearchPill added to FeedTabsHeader (Home/Feeds column headers)
- Result selection dispatches to deckState (deck mode) or singlePaneState (single-pane)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-29 07:02:49 +03:00
nrobi144andClaude Opus 4.6 eb114f0a58 refactor(desktop): share MainSidebar across single-pane and deck modes
- Rename DeckSidebar -> MainSidebar (it's not deck-specific anymore)
- Hoist MainSidebar from DECK-only to shared Row in Main.kt
- Remove inline 80dp NavigationRail from SinglePaneLayout
- SinglePaneLayout now only renders content pane (sidebar is external)
- onNavigate dispatches to singlePaneState.navigate() or deckState.addColumn()
  based on active layoutMode
- Active item highlighting works in both modes

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-29 07:02:49 +03:00
nrobi144andClaude Opus 4.6 b431c1efab feat(desktop): visual personality overhaul — unified theme, sidebar, cards
Phase 1: Replace per-OS color schemes with unified Amethyst brand
- Cyan/blue accent (#0096FF light, #4DB8FF dark) replacing OS-adaptive colors
- Amethyst purple as tertiary heritage color
- Unified shapes (8/12/16/24dp) replacing per-OS variants
- Standardized typography weights (Light for display, SemiBold for headlines)
- Letter spacing unified to -0.3sp

Phase 2: Spacing system
- AmethystSpacing CompositionLocal with design tokens
- LocalIsDarkTheme for M3-compatible dark mode detection

Phase 3: Sidebar redesign
- 240dp wide sidebar with icon + text labels (was 56dp icon-only)
- Animated collapse/expand with smooth width transition
- Avatar + username at top with account switcher
- Custom feeds section from FeedDefinitionRepository
- Active item cyan pill indicator with hover effects
- Collapse state persisted in Preferences
- Debounced fitColumnsToWidth to prevent animation thrash

Phase 4: Card refinement
- OutlinedCard with 1dp border replacing 1dp shadow elevation
- 16dp internal padding (was 12dp)
- Converted NoteCard, ReadsScreen, DraftsScreen, MyHighlightsScreen, UserProfileScreen

Phase 5: Column header restyling
- 48dp height (was 40dp) with surfaceContainer background
- 12dp horizontal padding (was 8dp)

Phase 6: Polish
- HoverModifiers.kt — shared hover highlight using onPointerEvent + drawBehind
- ShimmerPlaceholder.kt — skeleton loading animation in commons/commonMain

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-29 07:02:49 +03:00
Vitor PamplonaandGitHub 16afed0f3f Merge pull request #3097 from vitorpamplona/claude/intelligent-heisenberg-MkXpX
Fix scroll position restoration in feed lists on return
2026-05-28 21:28:52 -04:00
Claude f8ff9049d9 fix(onchain): highlight bolt for own onchain zaps + own pending in counter
Two parallel gaps to the cashu work, surfaced once the cashu side
was wired correctly:

1. The orange bolt highlight on the reaction row never lit up for
   onchain zaps. Note.isZappedBy checked LN zaps, NWC payments,
   and (since Phase 1) nutzaps — but never onchainZaps. And the
   fast-path gate in ObserveZapIconState shared the same blind
   spot. Add isOnchainZappedBy parallel to isNutzappedBy (same
   shape: any onchainZaps entry whose source.author matches the
   user and whose source event is newer than afterTimeInSeconds),
   and extend the gate with onchainZaps?.isNotEmpty().

2. The reaction-row counter included CONFIRMED onchain amounts
   via updateZapTotal (verifiedSats only, per NIP-BC) but not
   the signed-in user's OWN pending/unverified outgoing zaps.
   That created a UX mismatch: the gallery shows the user's own
   UNVERIFIED entry with its claimed sat amount immediately
   (the user knows what they sent), but the counter stays at 0
   until the chain catches up. Add
   Note.extraOwnPendingOnchainSats(loggedInPubKey) that sums
   claimedSats from non-CONFIRMED onchainZaps whose source.author
   matches the logged-in pubkey, and add it on top of zapsAmount
   in both AccountViewModel.calculateZapAmount paths and
   ObserveZapAmountText's no-zapPayments fast path. Other senders'
   non-confirmed entries still contribute 0, preserving the
   anti-spoof posture for incoming zaps.
2026-05-28 23:06:10 +00:00
Claude 02fd0780d5 fix(feeds): don't snap to top when returning from a post screen
StickToTopOnPrepend hardcoded `wasAtTop = true` on remember, so when the
user scrolled down, navigated to a post and came back,
rememberForeverLazyListState restored a non-zero offset but the helper
still believed the user was at the top. As soon as the head-of-feed key
emitted (first observation after recomposition), the LaunchedEffect
scrolled them back to 0.

Seed wasAtTop from the actual restored scroll position via an
initialAtTop lambda. The sampler's gesture-guarded false→true protection
against keyed-item shifts is unchanged.

Regression from #3088.
2026-05-28 23:01:49 +00:00
Claude aeadbbd276 Make TimeAgoFormatter + CalendarTimeFormat thread-safe
The module-level mutable SimpleDateFormat formatters in these files
were read concurrently — UI composition on the main thread, and
LocalCache.justVerify calling dateFormatter() from background event-
verification coroutines for failed-signature log lines. SimpleDateFormat
is not thread-safe (mutable internal Calendar), and updateFormattersIfNeeded
reassigned the field mid-format. Race produced corrupted timestamp
strings and occasionally NumberFormatException inside format().

Replace the shared-var pattern with a small LocaleAwareFormatter that
wraps a ThreadLocal<Pair<Locale, SimpleDateFormat>>. Each thread caches
its own instance and rebuilds lazily when Locale.getDefault() changes —
no locks, no contention, same allocation profile after warm-up.

Apply the same pattern to CalendarTimeFormat for consistency; today its
callers are all main-thread but the structure was identical.
2026-05-28 23:00:03 +00:00