Commit Graph
496 Commits
Author SHA1 Message Date
nrobi144andClaude Opus 4.6 bcf61d53ff feat(desktop): add drag-and-drop for avatar/banner, fix avatar overlay
- Wire DragAndDropTarget on avatar circle and banner area
- Image-only filter (jpg/png/gif/webp/avif)
- Visual drag-over feedback (primary border highlight)
- Fix avatar: only show placeholder icon when no image set
  (previously overlay was visible behind the loaded avatar)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-27 10:22:18 +03:00
nrobi144andClaude Opus 4.6 ce173debba fix(desktop): center avatar vertically and increase to 120dp
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-27 06:51:35 +03:00
nrobi144andClaude Opus 4.6 2e405f5644 fix(desktop): redesign avatar picker as tappable circle with upload overlay
Replace the awkward small icon button with a full 100dp tappable circle.
Shows surfaceVariant background when empty, semi-transparent overlay with
centered upload icon when image is present. Spinner replaces icon during
upload.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-27 06:46:29 +03:00
nrobi144 b608ca02fb Merge remote-tracking branch 'upstream/main' into feat/desktop-profile-editing
# Conflicts:
#	desktopApp/src/jvmMain/kotlin/com/vitorpamplona/amethyst/desktop/ui/UserProfileScreen.kt
2026-05-27 06:38:47 +03:00
Claude bbb7a1282e feat(music): wire MusicTrack/MusicPlaylist kinds into feed filters, search, and labels
Audit pass after LocalCache wiring: MusicTrackEvent (36787) and
MusicPlaylistEvent (34139) were materialized and rendered, but a dozen
filter sites still listed AudioTrackEvent.KIND alone, so music events
silently dropped out of feeds, search, notifications, and relay labels.

Mirrors the AudioTrackEvent pattern in every place AudioTrackEvent.KIND
or `is AudioTrackEvent` appears:

Feed display filters (acceptableEvent + ADDRESSABLE_KINDS):
- HomeNewThreadFeedFilter
- FollowPackFeedNewThreadFeedFilter
- UserProfileNewThreadFeedFilter
- UserProfileMutualFeedFilter
- HashtagFeedFilter
- GeoHashFeedFilter

Relay subscription kind lists:
- FilterPostsByGeohash (PostsByGeohashKinds)
- FilterPostsByHashtags (PostsByHashtagKinds2)
- FilterPostsByRelay (PostsByRelayKinds2)
- SearchPostsByText (SearchPostsByTextKinds1)
- Desktop SearchFilterFactory.defaultKindGroup1

Notifications:
- NotificationFeedFilter.ADDRESSABLE_KINDS (NOTIFICATION_KINDS picks
  this up automatically)

Relay information screen:
- kindDisplayName → R.string.kind_music_track / kind_music_playlist
  (with the two new string resources)
2026-05-27 01:17:53 +00:00
nrobi144andClaude Opus 4.6 e4d7cdd327 feat(desktop): full profile editing with 13 fields, image upload, NIP-05 verification
Replace the single-field display name AlertDialog with a comprehensive
profile editing Dialog supporting all 13 Nostr profile fields: name,
display name, about, avatar, banner, website, pronouns, NIP-05,
lightning address, LNURL, and NIP-39 social proofs (Twitter, GitHub,
Mastodon).

New shared EditProfileFields state holder in commons/commonMain using
MutableStateFlow (matching ChatNewMessageState pattern) benefits both
Android and Desktop platforms.

Desktop-native features:
- Blossom image upload via DesktopFilePicker + UploadOrchestrator
- Live NIP-05 verification with debounced network check
- Keyboard shortcuts: Ctrl+S/Cmd+S save, Esc cancel
- Unsaved changes confirmation dialog
- Collapsible social proofs section
- Avatar/banner URL live preview via AsyncImage
- ProfileBroadcastBanner for relay broadcast feedback

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-26 09:53:34 +03:00
nrobi144andClaude 64293f2fcc fix(desktop): fix text spacing in rich text viewer
Pure text paragraphs now render as a single Text composable instead
of individual words in FlowRow, eliminating unwanted inter-word gaps.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-05-25 09:50:43 +03:00
nrobi144andClaude d6875a144b feat(desktop): add @mention autocomplete to compose dialog
Type @ followed by a name in the compose/reply dialog to see a
dropdown of matching users from the local cache. Selecting a user
inserts their nostr:npub reference. Shows avatar + display name +
truncated npub.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-05-25 09:35:46 +03:00
nrobi144 76fdd5d25f Merge upstream/main into worktree-desktop-low-hanging-fruit 2026-05-25 06:45:44 +03:00
nrobi144andClaude 4d532e0698 fix(desktop): wire onHashtagClick, add RTL support, add identity fields
- Add onHashtagClick param to FeedNoteCard for hashtag navigation
- RTL paragraph alignment in DesktopRichTextViewer
- External identities (Twitter, GitHub, Mastodon) on profile card

Co-Authored-By: Claude <noreply@anthropic.com>
2026-05-25 06:43:21 +03:00
Vitor PamplonaandGitHub fc5587f46f Merge pull request #3038 from nrobi144/feat/desktop-wallet-zapping
feat(desktop): wallet zapping, LNURL-pay send, QR receive, and session persistence
2026-05-23 12:05:56 -04:00
Vitor PamplonaandGitHub 0461072254 Merge pull request #3041 from vitorpamplona/claude/jolly-cray-6vKga
Makes the NWC process less strict, while checking for inconsistencies after the request reply is processed.
2026-05-23 12:05:09 -04:00
nrobi144andClaude e33fef2ebd feat(desktop): rich text migration, copy raw JSON, and profile metadata
Replace desktop's 3-segment custom parser with commons' 23-segment
RichTextParser. Add DesktopRichTextViewer rendering all segment types:
hashtags, invoices (with NWC pay), cashu tokens, custom emoji, nowhere
links, emails, relay URLs, markdown, image galleries, and more.

Add 'Copy Raw JSON' to note overflow menu. Add nip05, website, and
lightning address to profile card with right-click copy support.

🤖 Generated with Claude Code

Co-Authored-By: Claude <noreply@anthropic.com>
2026-05-23 15:46:46 +03:00
nrobi144andClaude Opus 4.6 e4691f6d93 fix(desktop): remove obsolete ZapDialogLogicTest
Test referenced formatSats, DEFAULT_ZAP_AMOUNTS, and ZapType which
were removed/made private in upstream merge.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-23 15:45:54 +03:00
nrobi144andClaude Opus 4.6 2b14b77acf feat(desktop): support LNURL-pay and lightning addresses in send dialog
Rewrite SendDialog with sealed state machine that auto-detects input
type (BOLT11, LNURL bech32, lightning address). For LNURL/address:
resolves endpoint, shows amount form with min/max hint, optional
comment field, fetches invoice, then pays via NWC. Strips lightning:
URI prefix. Inline copiable errors with retry.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-23 15:40:19 +03:00
nrobi144 a5405fef34 Merge remote-tracking branch 'upstream/main' into feat/desktop-wallet-zapping
# Conflicts:
#	desktopApp/src/jvmMain/kotlin/com/vitorpamplona/amethyst/desktop/ui/FeedScreen.kt
#	desktopApp/src/jvmMain/kotlin/com/vitorpamplona/amethyst/desktop/ui/NoteActions.kt
2026-05-23 15:19:47 +03:00
nrobi144andClaude Opus 4.6 4936d187fe fix(desktop): improve send/receive dialogs and LNURL error surfacing
SendDialog: switch to Dialog+Card with X close, inline copiable error
messages, button resets to "Pay Invoice" on error for retry.

LightningAddressResolver: return error body from callback responses so
server error messages (e.g. "Recipient wallet error") surface to user
instead of generic "Failed to fetch invoice". Also check "message"
field in addition to "reason" for error extraction.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-23 15:17:08 +03:00
nrobi144andClaude Opus 4.6 00708d92d3 feat(desktop): redesign receive dialog with QR code and cleaner UX
Replace AlertDialog with Dialog+Card pattern. Invoice created state now
shows centered amount, description, 240dp QR code, and full-width
"Copy Invoice" button. Close via top-right X button. Input form gets
full-width "Create Invoice" button.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-23 14:35:18 +03:00
nrobi144andClaude Opus 4.6 562cd3355b fix(desktop): fix feed cold-boot race and remove NWC diagnostic println
Add LaunchedEffect that rescans cache when followedUsers populates after
startup, fixing empty feed when contact list arrives after initial scan.
Remove diagnostic println from NwcPaymentHandler.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-23 14:02:48 +03:00
nrobi144andClaude Opus 4.6 e690292bbd fix(desktop): fix nsec session not persisting across restarts
LoginScreen's fire-and-forget save coroutine used rememberCoroutineScope
which got cancelled when the composable left composition after login.
Move saveCurrentAccount() to onLoginSuccess in Main.kt which uses the
app-level scope that survives recomposition. Fixes both nsec login and
generate-new-account flows.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-23 14:02:36 +03:00
nrobi144andClaude Opus 4.6 3185df21b6 fix(desktop): reactive counters, quote boost, and boost detail popup
- Add kind 1 (replies) to interaction subscriptions
- Key count reads on FlowSet state for reactive updates
- Wire Quote menu item to ComposeNoteDialog with q-tag support
- Add BoostsPopup on long-press repost icon (who boosted)
- ComposeNoteDialog now accepts quoteOf param with nostr: URI pre-fill

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-22 14:03:48 +03:00
nrobi144andClaude Opus 4.6 27543ac304 feat(desktop): long-press details popups + right-click customize for note actions
- Long-press zap icon → floating popup with zap receipts (sender, amount, message)
- Long-press like icon → floating popup with reactions grouped by emoji
- Right-click like icon → emoji picker (DropdownMenu with 6 common emojis)
- Right-click repost icon → Repost/Quote options (DropdownMenu)
- Right-click zap icon → custom zap dialog (preserved existing behavior)
- Long-press reply → opens thread (same as click)
- ActivePopup sealed class ensures only one popup open at a time
- Popup + ElevatedCard for rich content, DropdownMenu for option lists
- combinedClickable with explicit ripple preserves IconButton UX
- PopupProperties(focusable = true) for desktop click-outside dismiss
- @Immutable on ZapReceipt for Compose stability
- Note param added to NoteActionsRow, passed from FeedScreen

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-22 07:00:31 +03:00
nrobi144andClaude Opus 4.6 746dab51b4 fix(desktop): fix NWC relay connection, disconnect crash, and balance error handling
- Remove premature ensureRelayConnected check — NostrClient connects
  on subscribe/publish via sendOrConnectAndSync
- Fix disconnect crash: use appScope instead of rememberCoroutineScope
  to survive recomposition when nwcConnection goes null
- Surface balance errors/timeouts as snackbars instead of silent swallow
- Add ensureRelayConnected helper to RelayConnectionManager
- Add Phase 2 embedded wallet research doc

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-22 06:23:05 +03:00
Claude 8ce0edeb2c fix(nwc): verify response author against expected wallet-service pubkey
The previous commit dropped `authors` and `#p` from the relay subscription
filter to match Primal's interop shape. Without those, the relay will
deliver any signed kind-23195 event that carries our request id in `#e`,
so an attacker who can observe the request on the relay could forge a
"response" with their own keypair: Amethyst would happily derive a shared
secret from `event.pubKey` (the attacker), decrypt the payload, and
display attacker-controlled balance/transaction data. Even worse,
`paymentTracker.onResponseReceived` removed the pending entry on first
match — so the legitimate wallet reply that followed was silently dropped.

Move the author check from the relay layer into NwcPaymentTracker:

  - `registerRequest` now requires the expected wallet-service pubkey
    (read from the request's `p` tag). LocalCache extracts it during
    `consume(LnZapPaymentRequestEvent)` and refuses to register if the
    request has no `p` tag.
  - `onResponseReceived` takes the response author and returns a sealed
    MatchResult of NoMatch / WrongAuthor / Matched. A WrongAuthor result
    leaves the pending entry in the map so the legitimate response can
    still resolve it.
  - Android LocalCache and DesktopLocalCache both adopt the new API and
    log a warning on suspected spoof attempts.

End-to-end the response is still encrypted under the per-connection shared
secret, so this is a second layer of defence rather than the only one,
but matching the author keeps a forged kind-23195 from consuming the
pending slot and DoSing the legitimate reply.
2026-05-21 21:30:16 +00:00
nrobi144andClaude Opus 4.6 704c8a3e87 fix(desktop): center wallet empty state vertically in column
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-21 13:50:46 +03:00
nrobi144andClaude Opus 4.6 4d95b7ed8a fix(desktop): adapt wallet screen to upstream AccountManager API changes
clearNwcConnection and setNwcConnection now require npub param and are
suspend functions. ConnectWalletDialog validates URI prefix inline
before firing the async connect callback.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-21 09:36:55 +03:00
nrobi144andClaude Opus 4.6 5bf4db4582 fix(desktop): wire zap types, relay hints, and add testing sheet
- Map Desktop ZapType (Public/Private/Anonymous) through to
  LnZapEvent.ZapType and pass to ZapAction.fetchZapInvoice
- Add relayHint + authorRelayHint params to NoteActionsRow;
  FeedScreen now passes Note.relayHintUrl() for reactions/reposts
- Replace metadata preload TODO with design rationale comment
- Add 70-test manual testing sheet for wallet & zapping features

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-21 09:31:39 +03:00
nrobi144andClaude Opus 4.6 a15ce33807 refactor(desktop): use dialogs for wallet actions, center home content
Send, Receive, and Connect are now AlertDialogs instead of full-screen
sub-pages. The wallet home content is centered with widthIn(max=360.dp)
for a polished desktop look. Dialogs are the native desktop pattern
(consistent with zap dialog, bookmark dialog, tor settings dialog).

- ConnectWalletDialog: NWC URI input with paste + validation
- SendDialog: BOLT11 invoice input with paste + progress
- ReceiveDialog: amount/description input, transitions to show
  generated invoice with copy button
- Home content: centered balance card + action buttons + connection info

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-21 09:31:39 +03:00
nrobi144andClaude Opus 4.6 420a10df50 fix(desktop): subscribe before publish in NWC RPC to avoid race condition
getBalance and makeInvoice were publishing the request event before
subscribing for the response. Fast wallet responses (like get_balance)
would arrive before the subscription was active, causing timeouts.

Fix: subscribe first via onSubscribed callback in waitForGenericResponse,
then publish. The original payInvoice wasn't affected because Lightning
routing takes long enough for the subscription to be ready.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-21 09:31:38 +03:00
nrobi144andClaude Opus 4.6 ed06bb2267 test(desktop): add wallet and zapping test coverage
- ZapDialogLogicTest: 12 tests for formatSats, DEFAULT_ZAP_AMOUNTS,
  ZapType enum (labels, descriptions, entries)
- NwcPaymentHandlerTest: 18 tests for PaymentResult, BalanceResult,
  InvoiceResult sealed types, NWC connection validation, response
  event structure
- NwcRpcIntegrationTest: 5 full round-trip tests using real crypto
  (pay_invoice, get_balance, make_invoice request/response cycles,
  NWC URI -> client pipeline)
- Make formatSats and DEFAULT_ZAP_AMOUNTS internal for test access

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-21 09:31:38 +03:00
nrobi144andClaude Opus 4.6 7bd5fb3122 feat(desktop): wire NWC wallet operations and AccountManager integration
- Wire AccountManager.setNwcConnection() and clearNwcConnection() into
  wallet column for persistent connect/disconnect
- Implement NwcPaymentHandler.getBalance() via NIP-47 get_balance RPC
- Implement NwcPaymentHandler.makeInvoice() via NIP-47 make_invoice RPC
- Add generic waitForGenericResponse() helper for NWC RPC operations
- Auto-fetch balance on wallet column load via LaunchedEffect
- Wire receive screen to generate real invoices via NWC
- All wallet column features now functional (no blocking TODOs)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-21 09:31:38 +03:00
nrobi144andClaude Opus 4.6 8313f0cf12 feat(desktop): add wallet column and enhanced zapping UX
Phase 1: Wallet deck column with NWC integration
- Add DeckColumnType.Wallet with full deck system integration
  (AppDrawer, ColumnHeader, DeckState persistence, MenuBar)
- WalletColumnScreen with 4 sub-screens: Home (balance + actions),
  Connect (NWC URI paste), Send (pay BOLT11), Receive (create invoice)
- Uses existing NwcPaymentHandler for payment execution

Phase 2: Zapping UX improvements
- Upgrade ZapAmountDialog with zap type selection
  (PUBLIC/PRIVATE/ANONYMOUS via FilterChips)
- Add custom amount input alongside preset chips
- One-click zap: left-click sends default amount via NWC,
  right-click opens custom zap dialog
- Configurable zap amounts parameter (no longer hardcoded)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-21 09:31:38 +03:00
nrobi144andClaude Opus 4.6 5ad21b6acd fix(desktop): disable ProGuard optimization entirely — fixes kmp-tor crash
Disabling individual sub-passes (method/specialization/returntype,
method/marking/static) was not sufficient. The interaction between
multiple optimization passes causes IncompatibleClassChangeError in
kmp-tor's AsyncFs.of() at launch. Shrink (dead code removal) stays
ON for the size win; only optimize is disabled.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-19 10:11:11 +03:00
nrobi144andClaude Opus 4.6 487dd3f2ac fix(desktop): disable method/marking/static ProGuard optimization
The smoke test discovered that ProGuard's method/marking/static pass
converts kmp-tor's AsyncFs.of() from instance to static. The JVM
then throws IncompatibleClassChangeError at launch because callers
still use invokevirtual.

Also updates smoke-test-desktop.yml trigger: runs on any PR touching
desktopApp/ (not just build config files).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-19 09:48:01 +03:00
nrobi144andClaude Opus 4.6 c0c055e771 fix(desktop): restore java.management module — confirms #2819 fix
Reverts the intentional breakage from d0a6bbc96. The smoke test
proved it catches the crash: removing java.management from jlink
modules causes a fatal IncompatibleClassChangeError in kmp-tor
at launch (the module is needed for ManagementFactory + tor runtime).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-19 09:26:26 +03:00
nrobi144andClaude Opus 4.6 d0a6bbc96f test(ci): intentionally remove java.management to verify smoke test catches #2819
This commit should FAIL the release-deb-launch CI job, proving the
smoke test works. Will be reverted in the next commit.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-19 09:05:10 +03:00
nrobi144andClaude Opus 4.6 cf6541a1e1 test(desktop): add Compose UI smoke test + release .deb launch CI
Fixes #2819 — v1.08.0 .deb crashed on Ubuntu with ManagementFactory
error because the build had no jlink modules() declaration. Current
main already has the fix; this PR adds CI to prevent regressions.

- Add compose.desktop.uiTestJUnit4 dependency
- DesktopLaunchSmokeTest: renders LoginScreen, asserts title text
- build.yml: xvfb for Linux leg so UI test runs on every PR
- smoke-test-desktop.yml: builds release .deb, installs it, launches
  under xvfb, verifies process stays alive 10s

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-19 07:09:16 +03:00
nrobi144andClaude Opus 4.6 de5dc34cb7 fix(desktop): fix macOS VLC bundled discovery and video rendering
MacOsVlcDiscoverer.setPluginPath() called LibC.INSTANCE.setenv() via
JNA, but macOS 13+ uses versioned symbols (setenv$3b99ba0d) that JNA
can't resolve, breaking all video playback without system VLC.

Changes:
- Replace LibC.setenv with direct JNA Function.getFunction("c","setenv")
  call that bypasses the problematic interface binding
- Store discoveredPluginPath for --plugin-path factory arg fallback
- Add -Dvlc.plugin.path JVM property as ultimate fallback
- Delete stale VLC plugin cache on macOS before factory creation
- Pass --plugin-path to audio factory too when env var fails
- Add jdk.unsupported module to jlink (VLCJ ByteBufferFactory needs
  sun.misc.Unsafe for video frame buffer allocation)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-18 16:52:43 +03:00
nrobi144andClaude Opus 4.6 935ad84ac3 fix(desktop): fix release build ProGuard rules that crash app on launch
ProGuard in release builds (packageReleaseDmg/Deb/Rpm) strips classes
accessed via reflection, JNI, or service loading — causing multiple
runtime failures:

- Jackson ExceptionInInitializerError: enum constants stripped, NPE in
  MapperConfig.collectFeatureDefaults() (#2929, 5000 sats bounty)
- JNA/VLCJ: static methods stripped, SIGABRT in native callbacks
- secp256k1/SQLite/kmp-tor: JNI loader classes stripped
- Coil/okhttp/okio: image loading and networking broken
- Kotlin metadata stripped: Jackson can't call default constructors

Changes:
- Upgrade ProGuard 7.7.0 → 7.9.1 (Kotlin 2.3 metadata support)
- Disable optimization (-dontoptimize) to prevent bytecode rewriting
  that produces VerifyError (Guardsquare/proguard#460)
- Add -keep rules for all JNI/reflection-dependent libraries
- Keep Kotlin @Metadata annotations for Jackson deserialization
- Suppress Kotlin 2.3 compile-time stub warnings

Tested: release DMG builds and launches without crash on macOS.

Closes #2929

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-18 16:52:27 +03:00
Claude 7ed93bcbc2 fix(desktop): package AppImage with appimagetool, not linuxdeploy
linuxdeploy auto-walks every binary in the staged AppDir with ldd to
bundle their shared-library deps. That fights jpackage's self-contained
output on two axes and aborts the createReleaseAppImage task before any
AppImage is produced:

  - The bundled JRE puts libjvm.so at usr/lib/runtime/lib/server/, while
    its sibling libs (libmanagement.so, libawt_xawt.so, libfontmanager.so
    and others in usr/lib/runtime/lib/) have RPATH=$ORIGIN. ldd cannot
    resolve libjvm.so from those libs without help, so linuxdeploy errors:
        ERROR: Could not find dependency: libjvm.so
        ERROR: Failed to deploy dependencies for existing files

  - The bundled libvlc plugins under usr/lib/app/resources/vlc/ are
    UPX-compressed by the vlc-setup plugin. patchelf cannot read their
    section headers, so linuxdeploy aborts:
        ERROR: Call to patchelf failed:
        patchelf: no section headers. The input file is probably a
        statically linked, self-decompressing binary

  - Even after working around libjvm.so via LD_LIBRARY_PATH, several VLC
    libs have RUNPATH pointing at the wrong directory, so ldd fails on
    libva.so.2 / libvlccore.so.9 next.

This is why v1.09.2 shipped .deb + .rpm from the linux leg but no
.AppImage and no linux .tar.gz from the linux-portable leg — the gradle
step failed on linuxdeploy, so the subsequent "Build portable archives"
step never ran.

Swap to appimagetool, which only embeds the AppDir as-is into a
SquashFS-backed, runtime-prepended AppImage and never touches the
contents. jpackage already bundles a complete, self-contained app —
we don't need linuxdeploy's dep-discovery. AppRun continues to handle
LD_LIBRARY_PATH at launch.

Verified locally on ubuntu-24.04: a fresh
:desktopApp:createReleaseAppImage now produces a valid 254 MB
Amethyst-1.09.2-x86_64.AppImage in ~30 s on top of an up-to-date
createReleaseDistributable.

Side changes:
- Workflow installs desktop-file-utils (appimagetool 1.9.0 calls
  desktop-file-validate on the .desktop entry).
- BUILDING.md updated with the new local-dev fetch command.
- .gitignore replaces the linuxdeploy paths with appimagetool's.
2026-05-16 19:04:07 +00:00
Claude 2e8bf0d45d build: remove unused :ammolite module
The :ammolite module contained no production Kotlin/Java sources (just
a manifest, build.gradle, and proguard stubs) and no module in the
codebase imports com.vitorpamplona.ammolite.*.

Removes:
- ammolite/ directory (5 files)
- :ammolite project include in settings.gradle
- implementation project(':ammolite') from :amethyst
- androidTestImplementation project(':ammolite') from :benchmark
- :ammolite:testDebugUnitTest from CI workflow and pre-push hook
- -keep class com.vitorpamplona.ammolite.** rules from
  :amethyst, :commons, and :desktopApp proguard files
- Stale references in CONTRIBUTING.md, CLAUDE.md, and the
  gradle-expert skill dependency-graph doc

Small build-graph win: one fewer module to configure, compile, lint,
and spotless-check on every build, and one fewer unit-test target in
both CI and the local pre-push hook.
2026-05-16 14:40:03 +00:00
Vitor PamplonaandGitHub 50b69b1ca5 Merge pull request #2924 from mstrofnone/feat/desktop-render-import-follow-list-dialog
feat(desktop): wire Import Follow List dialog into UI
2026-05-15 22:39:48 -04:00
Vitor PamplonaandGitHub 907dead3a0 Merge pull request #2923 from mstrofnone/feat/desktop-namecoin-settings-ui
feat(desktop): wire NamecoinSettingsSection into Settings screen
2026-05-15 22:39:13 -04:00
m 931a217251 fix(desktop): mirror Android ProGuard strategy for release builds
Compose Multiplatform 1.11.0 wired ProGuard 7.7.0 into the release
build for the first time. With optimize + shrink + obfuscate all on,
the desktop v1.09.1 DMG hit four distinct runtime failures:

  A. Jackson's SerializationFeature.values()/valueOf() stripped, so
     Class.getEnumConstants() returned null and ObjectMapper failed
     to initialise (app didn't launch). Fixed in #2921.
  B. fr.acinq.secp256k1.* JNI bridge classes renamed by obfuscate;
     bundled libsecp256k1-jni.dylib could not FindClass the original
     names, so KeyPair / sign / verify crashed on first use.
  C. androidx.sqlite-bundled native declarations (nativeThreadSafeMode,
     nativeOpen) shrunk away because no Kotlin caller referenced them
     directly; libsqliteJni.dylib raised NoSuchMethodError on the first
     EventStore query.
  D. ProGuard's method/specialization/returntype optimize sub-pass
     generated a synthetic okio bridge (Okio__OkioKt.buffer$<hash>)
     whose declared return type was RealBufferedSource but whose body
     returned the BufferedSource super-interface. The JVM verifier
     rejected the bridge, killing every OkHttp connection.

The Android (mobile) module solved the same class of problems in
amethyst/proguard-rules.pro with a single global strategy:

    -dontobfuscate
    -keepnames class ** { *; }
    -keep enum ** { *; }
    + per-library -keep rules for JNA / libsodium / libscrypt
    + first-party -keep com.vitorpamplona.**

This commit replays that strategy in desktopApp/compose-rules.pro
and drops the previous optimize.set(false)/obfuscate.set(false)
escape hatch in desktopApp/build.gradle.kts. Shrink and optimize
both stay on; only the one optimize sub-pass that produced invalid
okio bytecode (method/specialization/returntype) is disabled.

Additional desktop-only keep rules (Jackson, full JNA, VLCj,
SLF4J, OkHttp / Conscrypt / BouncyCastle / OpenJSSE / Graal
dontwarns) stay in place. Two libraries shipped only on desktop
also get explicit -keep rules so their native callbacks survive
shrink:

  - androidx.sqlite.** + native <methods>; (nativeThreadSafeMode
    was previously stripped even with -keepnames, because shrink
    removes members the global rule doesn't cover).
  - pt.davidafsilva.apple.** + native <methods>; for the macOS
    Keychain JNI bridge.

Verified on macOS arm64 packageReleaseDmg:
  - javap on the post-ProGuard jars confirms:
      * fr.acinq.secp256k1.NativeSecp256k1 keeps its FQCN.
      * SerializationFeature.values() / valueOf() present.
      * androidx.sqlite.driver.bundled.BundledSQLiteDriverKt
        retains native nativeThreadSafeMode() + nativeOpen().
      * Okio__OkioKt only exposes buffer(Source): BufferedSource
        and buffer(Sink): BufferedSink — no specialized
        buffer$<hash> bridge.
  - Bundled app launches and reaches VLC MediaPlayerFactory init
    with zero VerifyError / NoSuchMethodError / UnsatisfiedLinkError
    in the log.
2026-05-16 11:38:15 +10:00
m 33c97c3991 feat(desktop): wire Import Follow List dialog into UI
The ImportFollowListDialog composable was already implemented but never
rendered. The File menu item set a boolean state that no observer
consumed.

Render the dialog from MainContent inside the CompositionLocalProvider
that supplies LocalNamecoinService, so Namecoin (.bit, d/, id/)
identifier resolution works in addition to npub/hex/NIP-05.

Also add a left-side launcher in both layouts so the feature is
discoverable without using the File menu:
- single-pane: NavigationRailItem (PersonAdd icon, 'Import' label)
- deck: IconButton in the DeckSidebar next to 'Add Column'
2026-05-16 11:25:46 +10:00
m d1f037c678 feat(desktop): wire NamecoinSettingsSection into Settings screen
The desktop client already ships DesktopNamecoinPreferences, the
DesktopNamecoinNameService that consumes them, and the NamecoinSettingsSection
composable that's a port of the Android UI. The section just wasn't surfaced
in the desktop Settings screen.

This wires NamecoinSettingsSection into RelaySettingsScreen, between the Tor
section and the Developer / Relay sections. Preferences come from the
namecoinPreferences parameter that the deck container already passes in, and
fall back to LocalNamecoinPreferences when called from another caller.

User-visible behaviour: the .bit / d/ / id/ ElectrumX server settings (master
toggle, custom servers, defaults indicator, add/remove, reset) are now
reachable from desktop Settings and persist via java.util.prefs.Preferences.
2026-05-16 10:09:12 +10:00
m 31e73e3865 fix(desktop): add ProGuard keep rules for reflection-heavy deps
The v1.09.1 desktop release (DMG/MSI/DEB/RPM) fails to launch on every
platform with:

  Exception in thread "main" java.lang.ExceptionInInitializerError
      at com.fasterxml.jackson.databind.ObjectMapper.<init>(ObjectMapper.java:700)
      at com.vitorpamplona.amethyst.desktop.ui.deck.DeckState.<clinit>
  Caused by: java.lang.NullPointerException:
      Cannot read the array length because the return value of
      "java.lang.Class.getEnumConstants()" is null
      at com.fasterxml.jackson.databind.cfg.MapperConfig
              .collectFeatureDefaults(MapperConfig.java:107)
  Failed to launch JVM

Root cause: PR #2914 wired Compose 1.11.0's new ProGuard pass into the
release build but added only `-dontwarn` rules. ProGuard then optimized
Jackson's enum classes (`SerializationFeature`, `MapperFeature`, etc.) and
stripped their synthetic `values()` / `valueOf()` methods because nothing
in Amethyst's own bytecode calls them. Jackson does call them — but
reflectively, at `ObjectMapper` construction time, long after ProGuard
finished. The result is `Class.getEnumConstants()` returning null and the
JVM giving up.

The same regression silently broke VLC media playback (VLCj's
`ServiceLoader` lookup of `DiscoveryDirectoryProvider` impls printed
`ConfigDirConfigFileDiscoveryDirectoryProvider not found` because
ProGuard stripped both the META-INF/services file and the impl class).

Fix: add explicit `-keep` rules for the four reflection-heavy libraries
on the desktop classpath:

- Jackson (databind, core, annotations, module-kotlin): keep classes
  intact and keep `values()`/`valueOf()` on every Jackson enum.
- JNA (com.sun.jna.\*\*): used by VLCj and kmp-tor — its `Structure`
  field-order reflection requires field metadata to survive.
- VLCj (uk.co.caprica.vlcj.\*\*): `ServiceLoader`-based discovery providers
  and reflective binding classes throughout.
- SLF4J: detected by Jackson via `Class.forName`.

Verified by building `:desktopApp:createReleaseDistributable` and
launching the resulting bundle on macOS arm64:
  - No `ExceptionInInitializerError`
  - `VLC: bundled discovery succeeded`
  - `VLC: MediaPlayerFactory created successfully`

The same ProGuard pass runs on all four target formats (Dmg, Msi, Deb,
Rpm) so this single change fixes the regression on every desktop OS.
2026-05-16 09:13:56 +10:00
Claude ffd64a2e70 fix(desktop): add project ProGuard rules to unblock release packaging
Compose Multiplatform 1.11.0 tightened its default ProGuard ruleset so
`proguardReleaseJars` now fails on unresolved references that 1.10.3
tolerated silently. The v1.09.0 release tag-build hit this on every
desktop leg (macOS DMG, Windows MSI, Linux DEB/RPM, Linux AppImage),
which is why only Android APKs and the macOS-arm64 / Linux Amy CLI
bundles made it onto the GitHub release.

Three groups of references were flagged:

  - Jackson kotlin-module 2.21.x value-class converters
    (`IntValueClassBoxConverter`, `ValueClassKeyDeserializer$*`, …)
    invoke `MethodHandle.invokeExact` with polymorphic signatures
    ProGuard cannot resolve against the abstract MethodHandle.
  - okhttp's optional TLS adapters (`okhttp3.internal.graal.*`,
    `BouncyCastle/Conscrypt/OpenJSSEPlatform`) reference classes from
    runtime backends that aren't on the desktop classpath; okhttp falls
    back to the JDK default in their absence.
  - JNA's `com.sun.jna.internal.Cleaner` inner classes touch synthetic
    `access$N` accessors that ProGuard's class-member analysis cannot
    follow.

Add `desktopApp/compose-rules.pro` with `-dontwarn` rules for each
bucket and wire it through `compose.desktop.application.buildTypes
.release.proguard.configurationFiles`. Verified locally by re-running
`:desktopApp:proguardReleaseJars` (now BUILD SUCCESSFUL) and
`:desktopApp:packageReleaseDeb` (gets past ProGuard; only fails on the
sandbox missing `fakeroot`, which CI installs in the existing
"Install RPM tooling" step).
2026-05-15 21:03:28 +00:00
nrobi144andClaude Opus 4.6 325a1f6f6d fix: address code review — NWC key cleanup, CancellationException rethrow
- removeAccountFromStorage now deletes nwc_<npub> keychain entry
  (fixes orphaned wallet key on account removal)
- Rethrow CancellationException in loadSavedAccount and loginWithKey
  catch blocks (fixes broken structured cancellation)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-15 11:33:16 +03:00
nrobi144andClaude Opus 4.6 46caa4d795 fix(desktop): harden account security — NWC to keychain, single source of truth
CRITICAL: Move NWC wallet secret from plaintext nwc_connection.txt to OS
keychain. The NWC secret is a private key that can authorize Lightning
payments — storing it in plaintext allowed any process to steal funds.

Security fixes:
- NWC secret stored in OS keychain as "nwc_<npub>" (per-account)
- accounts.json.enc is now the sole source of truth for cold boot
- Eliminate bunker_uri.txt, last_account.txt, nwc_connection.txt
- Legacy files deleted on first startup (one-time cleanup)
- logout(deleteKey=true) now removes account from accounts.json.enc
- Corrupted accounts.json.enc backed up as .corrupt.<timestamp>

Cold boot rewrite:
- loadSavedAccount() routes by SignerType from accounts.json.enc
- No longer reads stale bunker_uri.txt (fixes nsec→bunker confusion)
- No longer reads last_account.txt (uses activeNpub from metadata)

Multi-account improvements:
- NWC connections are per-account (switch account = switch wallet)
- Each account type (Internal/Remote/ViewOnly) loads correctly
- saveBunkerAccount() no longer writes to bunker_uri.txt

Updated 8 existing test files to use accountStorage instead of
writing legacy files directly.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-15 11:29:42 +03:00