Commit Graph
15243 Commits
Author SHA1 Message Date
David KasparandGitHub 80b9c91c59 Merge pull request #3174 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2026-06-11 13:45:08 +02:00
Róbert NagyandGitHub e3441157ad Merge branch 'main' into feat/desktop-vlcj-to-kdroidfilter 2026-06-11 13:50:42 +03:00
nrobi144 704f4f44ee feat(desktop): replace vlcj with kdroidFilter ComposeMediaPlayer + JCodec/FFmpeg
Drops uk.co.caprica:vlcj 4.8.3 (GPL-3.0) from desktopApp and replaces it
with an MIT-dominant stack:

- Video / audio playback: io.github.kdroidfilter:composemediaplayer:0.10.0
  (MIT) — OS-native backends (Media Foundation on Windows, AVFoundation on
  macOS, GStreamer on Linux). First-class Compose VideoPlayerSurface.
- Thumbnail extraction: org.jcodec:jcodec(+javase):0.2.5 (BSD-2) primary
  H.264 path, raw ProcessBuilder FFmpeg fallback for HEVC / VP9 / AV1 /
  HLS / non-faststart MP4.
- Binary SPDX: MIT AND LGPL-2.1-or-later AND BSD-2-Clause AND Apache-2.0.
  rpmLicenseType updated accordingly (previously misdeclared as MIT
  while shipping GPLv3 vlcj).

Code changes:
- Deleted: VlcjPlayerPool, MacOsVlcDiscoverer, BundledVlcDiscoverer,
  VlcResourceResolver
- Rewrote: GlobalMediaPlayer (kdroidFilter engine + snapshotFlow-based
  state sync into the preserved MediaPlaybackState contract);
  VideoThumbnailCache (JCodec → ProcessBuilder ffmpeg cascade, with a
  hard 4 MiB download cap, Content-Type sniff to reject HTML error
  pages, and cleanup of zero-byte cache entries); DesktopVideoPlayer
  (mounts VideoPlayerSurface for the active URL, codec/network error
  UX with "Open in default player" fallback)
- Updated: NowPlayingBar + GlobalFullscreenOverlay to render
  VideoPlayerSurface directly (drops the videoFrame ImageBitmap relay)
- Main.kt: drops vlcj pre-init / shutdown calls (kdroidFilter lazy-loads
  natives + registers its own shutdown hook on Windows)

Build / packaging:
- Removes ir.mahozad.vlc-setup plugin + vlcSetup{} block + the per-OS
  bundled VLC tree + the -Dvlc.plugin.path JVM arg
- Adds NOTICE.md + per-component LICENSE-*.txt under appResources/common
  (LGPL-2.1 license text is a placeholder — replace with verbatim FSF
  text before release)
- Adds per-OS LGPL FFmpeg drop-in directories with README pointing at
  the recommended LGPL binary source (osxexperts.net / Crigges Windows
  LGPL build)
- Adds Flathub manifest skeleton (Gitnuro-style: org.freedesktop.Platform
  24.08 + openjdk21 extension + org.freedesktop.Platform.ffmpeg-full
  add-extension for patent codecs)
- AppRun: drops VLC LD_LIBRARY_PATH / VLC_PLUGIN_PATH env wiring

Verified on macOS arm64:
- ./gradlew :desktopApp:compileKotlin                BUILD SUCCESSFUL
- ./gradlew :desktopApp:test                         BUILD SUCCESSFUL
- ./gradlew :desktopApp:spotlessApply                clean
- Smoke launch: no VLC/vlcj/libvlc log lines, kdroidFilter native
  library extracts to ~/.cache/composemediaplayer/native/, thumbnail
  cache populates at ~/.cache/amethyst-desktop/video-thumbs/ with the
  4 MiB cap enforced
- H.264 MP4 playback (active + thumbnail extraction) confirmed
- VP9-in-WebM playback fails on macOS as AVFoundation cannot decode it —
  expected codec gap; surfaced via PlaybackErrorMessage + "Open in
  default player" handoff in DesktopVideoPlayer

Docs:
- docs/plans/2026-06-11-feat-replace-vlcj-with-kdroidfilter-plan.md
- docs/plans/2026-06-11-vlcj-replacement-testing-sheet.md
2026-06-11 13:37:33 +03:00
Claude 049db06844 feat: pin DM conversations to the top of the chat list
Long-pressing a private chat row in the messages tab now offers Pin to
top / Unpin. Pinned rooms sort above everything else in the known-chats
list (ties broken by the usual newest-first order) and show a small pin
icon next to the room name.

Pins are stored per account as a local-only setting (encrypted
SharedPreferences via AccountSettings.pinnedChatrooms) because there is
no standard NIP-51 list for pinned DMs; this can be migrated to a synced
list later if one is standardized.

https://claude.ai/code/session_0131YwG6bE3yH8Kk9MxjMA5i
2026-06-11 01:36:16 +00:00
Claude 7050264d70 fix: clear cachedAccounts entry when an account is deleted
LocalPreferences.deleteAccount() wiped the encrypted preference file but
left the in-memory cachedAccounts entry behind, so deleting and re-adding
the same account could resurrect stale settings from the cache.

https://claude.ai/code/session_0131YwG6bE3yH8Kk9MxjMA5i
2026-06-11 01:36:03 +00:00
Claude 0bfd2f8bc2 test: move MarmotManagerLeaveRejoinTest to jvmTest so CI actually runs it
The test lived in commons androidHostTest, but no CI workflow or
pre-push task runs :commons:testAndroidHostTest — and running it
manually fails before reaching any assertion: quartz's android
PlatformLog actual hits unmocked android.util.Log stubs
(NoSuchMethodError), since the source set is not configured with
returnDefaultValues. The end-to-end leave/rejoin coverage was
therefore never executed anywhere.

:commons:jvmTest runs in CI and in the pre-push hook, already has the
secp256k1 JVM bindings the test needs, and uses quartz's JVM logger.
Verified green there alongside MarmotManagerRestoreTest.
2026-06-11 01:20:59 +00:00
Claude c6b09c4b53 fix: anonymous profile zaps were encrypted as private zaps; nutzap chips reply on long-press
The user-only LnZapRequestEvent.create overload marked ANONYMOUS requests
with a blank-valued anon tag, which the signer treats as an unsigned
private zap: the message was encrypted to the recipient under the
throwaway key instead of staying public. Use the valueless anon tag, as
the event-targeted overload already does. Adds a regression test.

Also carries the nutzap note into the notification gallery chips so the
long-press reply-to-zap gesture works for NIP-61 nutzaps too — no extra
tagging needed there since nutzaps are signed by the sender.

https://claude.ai/code/session_01LM3KTECMMAdNBHZfs1dANa
2026-06-10 23:41:26 +00:00
Crowdin Bot ea74be65cf New Crowdin translations by GitHub Action 2026-06-10 23:30:11 +00:00
Vitor PamplonaandGitHub 2e26823f3d Merge pull request #3166 from davotoula/fix/nip71-legacy-video-addressable-dtag
Compute legacy NIP-71 video addresses with their d tag
2026-06-10 19:28:27 -04:00
Vitor PamplonaandGitHub 765572b25b Merge pull request #3173 from vitorpamplona/claude/lucid-bardeen-tiedwa
Audit & refresh skill library, docs, and hooks (Phase 2–3)
2026-06-10 19:28:18 -04:00
Claude c503af0f5a docs: fix stale signer, NIP-19, NIP-44, and EventStore claims in skills
Second audit pass over the remaining skills (amy-expert, auth-signers,
find-*, nostr-expert, quartz-integration, vendored technique skills),
verifying every concrete claim against the code:

- auth-signers: bunker login goes through NostrSignerRemote.fromBunkerUri
  + connect(), not the nonexistent RemoteSignerManager.connect(url)
- nostr-expert: NIP count 57 -> 80+; replace invented Nip44v2/Nip19
  static APIs with the real Nip44 facade, ByteArray bech32 extensions,
  entity create() helpers, and Nip19Parser.uriToRoute()?.entity
- nip-catalog: heading counts corrected to 87 standard + 23 experimental
  packages with a ground-truth pointer
- quartz-integration: NIP-19 example rewritten for ParseReturn.entity;
  Event Store is commonMain (all platforms), not Android-only, with the
  real store.sqlite.EventStore import and suspend query<T> API

amy-expert, find-missing-translations, find-non-lambda-logs, the rest of
auth-signers, and the vendored technique skills audited clean.

https://claude.ai/code/session_01EC7LdXjatFTh1CJSP4qKRn
2026-06-10 23:21:30 +00:00
Claude 2eb6510eec fix: stop refetching the full MLS kind:445 backlog on every restart
The Marmot subscription since, the processed-event dedup set, and the
application ratchet position (group state persists only at commits) are
all in-memory only. On restart, relays therefore redeliver the group's
entire kind:445 history and the rewound ratchet re-decrypts old
application messages as if they had just arrived — wasted decryption
work and, when a replay beats the disk restore, duplicate entries
appended to the persisted plaintext message log.

Two defenses:

- MarmotManager.restoreAll() now seeds each restored group's
  subscription since from the newest persisted decrypted message, minus
  a one-day overlap window for late/out-of-order publishes. Seeding
  happens before syncWithGroupManager registers default entries, so
  even the first filter set sent to relays carries it. The CLI is
  unaffected: it builds group filters from its own persisted since.

- MarmotMessageStore appends are now explicitly idempotent (contract
  was previously ambiguous and both real stores appended blindly):
  the Android and CLI file stores skip an entry that is already in the
  group's log, so replays inside the overlap window cannot grow it.

Covered by MarmotManagerRestoreTest in commons jvmTest — placed there
rather than androidHostTest because CI only runs :commons:jvmTest (the
androidHostTest task currently fails on android.util.Log stubs even
for the pre-existing Marmot test).
2026-06-10 23:03:48 +00:00
Claude e4ce7b887d feat: support replying to zaps from the notification screen
Zap receipts (kind 9735) are signed by the recipient's lightning provider,
not by the person who zapped, so both the reply tagging and the rendering
around replies-to-zaps need the sender resolved from the embedded kind 9734
zap request:

- Long-press on a zap chip in the notification galleries (MultiSetCard and
  ZapUserSetCard) opens the NIP-22 comment composer targeting the zap
  receipt, reusing the existing generic-comment fallback in routeReplyTo.
- CommentPostViewModel now p-tags the zap request author when replying to a
  zap so the zapper actually gets notified (the receipt's own author tags
  point at the custodian). Requests carrying an anon tag (anonymous or
  private zaps) are skipped: the embedded key is ephemeral and tagging the
  decrypted sender of a private zap would publicly expose them.
- The notifying chip row shows the zapper and removing the chip is
  respected, including across draft reload.
- FirstUserInfoRow and the compact reply-to label now display the zap
  sender (decrypted for private zaps, locally only) instead of the wallet
  service when the note or the replied-to parent is a zap receipt.
- ZapAmountCommentNotification carries the receipt note so chips can act on
  the zap itself.
- Adds LnZapRequestEvent.hasAnonTag() with tests covering public, anonymous
  and private zap requests.

https://claude.ai/code/session_01LM3KTECMMAdNBHZfs1dANa
2026-06-10 22:55:25 +00:00
Claude af7d61f361 fix: persist MLS chat unread state across app restarts
The unread dot for Marmot/MLS group rooms was driven by an in-memory
unreadCount on MarmotGroupChatroom. On restart the MLS group state is
restored from the last persisted commit, the kind:445 subscription
restarts with since=null, and the in-memory processed-event dedup set is
empty — so relays redeliver old group events, they re-decrypt as fresh
application messages, and the counter was re-bumped, resurrecting the
dot for chats already read.

Marking-as-read was already persisted: opening a group chat writes the
newest rendered message's createdAt to the MarmotGroup/<groupId> route
in lastReadPerRoute (saved to disk with account settings). Compute the
unread indicators from that timestamp instead — exactly how DM rooms
and public channels do it — in both the Messages screen row and the
Marmot group list row.

With no consumer left, drop the volatile counter and collapse the
addMessageSync/restoreMessageSync split (they only differed in the
counter bump).
2026-06-10 22:08:51 +00:00
Claude 8209f4416a docs: fix stale claims in Claude skills against current code
Audit pass that verified every concrete claim in .claude/ against the
repository:

- account-state: Account.kt no longer exposes followListFlow-style
  StateFlows; document the state-object pattern (kind3FollowList,
  muteList, bookmarkState, ... each exposing .flow) and rewrite the
  catalog reference from the real Account.kt
- feed-patterns: filter bases (FeedFilter, AdditiveFeedFilter,
  ChangesFlowFilter, FeedContentState) moved to commons/ui/feeds;
  ui/dal keeps AdditiveComplexFeedFilter/FilterByListParams plus
  back-compat typealiases; fix recipe example signatures
- relay-client: add nip17Dm/, eoseManagers and subscriptions entries
  to the layout tree
- gradle-expert: 4-module claim -> 10 modules; refresh compose/kotlin/
  BOM versions; rewrite dependency graph with verified edges for cli,
  geode, quic, nestsClient, quic-interop, benchmark
- desktop-expert: drop drifted Main.kt line numbers; sidebar is the
  custom MainSidebar in DeckSidebar.kt, not a NavigationRail in
  SinglePaneLayout.kt
- android-expert: compileSdk/targetSdk 36 -> 37, versionName via
  generateVersionName()
- kotlin-expert: remove reference to nonexistent commit 258c4e011
- CLAUDE.md: add missing geode/benchmark/quic-interop modules
- desktop-run: packageRpm + correct binaries output path; extract.md:
  drop duplicated find clause
- session-start.sh: /home/user/Amber fallback was a copy-paste from
  another repo; fall back to CLAUDE_PROJECT_DIR

https://claude.ai/code/session_01EC7LdXjatFTh1CJSP4qKRn
2026-06-10 22:01:29 +00:00
Claude f2cce3dc87 fix(clink): run offer/debit payer crypto off the Main thread
StrictMode flagged the offer round-trip (ephemeral keygen, JSON serialization,
NIP-44 encrypt/decrypt, signing) running on the UI thread, because
ClinkOfferPreview launches it from a Compose (Main) scope. Wrap the heavy work
in withContext(Dispatchers.IO) in both ClinkOfferPayer.requestInvoice and
ClinkDebitPayer.payInvoice/requestBudget so the payers are main-safe regardless
of caller dispatcher.

https://claude.ai/code/session_01NM2TyJtosLdY5ycjyabSRS
2026-06-10 21:40:53 +00:00
Claude 5aab19e8da feat(clink): copy-offer button on the offer card title
Adds a ContentCopy IconButton at the right of the CLINK Offer card title that
copies the noffer string (the active pointer, after any moved-offer redirect) to
the clipboard with a confirmation toast.

https://claude.ai/code/session_01NM2TyJtosLdY5ycjyabSRS
2026-06-10 21:36:27 +00:00
Claude d0af07be02 feat(cli): offer discover <nip05> — resolve a profile offer via NIP-05
Mirrors the app's NIP-05 .well-known clink_offer discovery fallback (kind-0
offers are already readable via 'amy profile show'). Reuses the Context's
nip05Client.loadClinkOffer and decodes the resolved noffer into its fields.

Adds a bad-nip05 validation case to the headless harness; 17/17 pass.

https://claude.ai/code/session_01NM2TyJtosLdY5ycjyabSRS
2026-06-10 21:30:12 +00:00
Claude 2635bd90a5 feat(cli): zap --with <ndebit> settles the invoice via CLINK debit
amy zap printed the invoice but never paid it. With --with <ndebit> it now
settles the fetched BOLT-11 in-place through a CLINK debit pointer (kind-21002,
reusing DebitCommands.settle), mirroring how the app routes a zap through its
default payment source. Works for both single-recipient (zap user) and
split zaps (zap event) — each recipient reports paid + preimage (or pay_error).

Adds a --with validation case to the headless harness; 16/16 pass.

https://claude.ai/code/session_01NM2TyJtosLdY5ycjyabSRS
2026-06-10 21:27:36 +00:00
Claude e77658c292 feat(cli): close CLINK parity gaps — profile offer, follow, offer pay, GFY detail
Brings amy's CLINK surface closer to the app's:

- profile edit --clink-offer <noffer|"">: set/clear the kind-0 clink_offer
  (validated as a real noffer; "" clears). MetadataEvent already carried the field.
- offer request --follow: chase an 'Expired or Moved' (code 3) reply to its
  'latest' pointer (bounded hops), mirroring the app; the error output now also
  carries code/latest/range so a script can follow or correct manually.
- offer pay <noffer> --with <ndebit> [--amount]: end-to-end — fetch the invoice
  (21001) and settle it through a debit pointer (21002), reusing DebitCommands.settle.
- Structured GFY detail (code, range, retry_after, delta) in debit/offer errors,
  via a new Output.error(extra=) overload.

Adds local-validation cases to the headless harness (offer pay --with, profile
edit --clink-offer); 15/15 pass.

https://claude.ai/code/session_01NM2TyJtosLdY5ycjyabSRS
2026-06-10 21:23:51 +00:00
Claude 5dadff0315 fix(clink): label the offer chip/card 'CLINK Offer'
https://claude.ai/code/session_01NM2TyJtosLdY5ycjyabSRS
2026-06-10 20:58:54 +00:00
Claude 01cb45cb31 feat(clink): render profile offer as a tappable chip, not an always-on card
The profile CLINK offer showed the full ClinkOfferPreview payment card up front.
Render it instead as a compact payment-target-style chip (Bolt icon + 'Lightning
Offer' label, matching the PaymentTargetChip look); tapping it expands the
payable card, collapsed by default — same expand-on-click idiom as the lightning
address row.

https://claude.ai/code/session_01NM2TyJtosLdY5ycjyabSRS
2026-06-10 20:55:21 +00:00
Claude f725b2ee39 chore: prune Claude config for Fable 5 and fix stale skill metadata
- CLAUDE.md: drop the 5-step skill-approval workflow (skills auto-trigger
  and the approval loop blocked autonomous sessions), condense Verify-Don't-
  Guess to the repo-specific tooling pointers, remove references to the
  uncommitted /bugfix and /investigate skills, and replace the mandated
  emoji survey matrix with one-line guidance
- android-expert / desktop-expert: add missing YAML frontmatter so the
  skills carry trigger descriptions and can actually auto-invoke
- extract.md: fix stale shared-ui/ module name -> commons/
- delete skills/quartz-kmp.md breadcrumb (migration long complete)
- gate the Stop spotlessApply hook on modified Kotlin files via
  hooks/stop-spotless.sh so Q&A-only turns skip the Gradle run
- condense core-skills-plan.md to a historical changelog

https://claude.ai/code/session_01EC7LdXjatFTh1CJSP4qKRn
2026-06-10 20:00:43 +00:00
Claude 1490e7a030 fix(zap): thread-safe progress accumulator for both pay rails
progressAllPayments was a non-atomic Float var incremented from the concurrent
mapNotNullAsync bodies AND the async response callbacks (NWC onResponse / the
CLINK launched coroutine), so parallel zap splits raced and could leave the
progress bar below 100%. Replace it with a shared PaymentProgress(AtomicInteger
over 2*N half-steps) used by both payViaNWC and payViaClinkDebit, which also
removes the duplicated half-step arithmetic.

Note: NWC's response half-step still won't fire if a wallet never replies within
its 60s window (sendZapPaymentRequestFor doesn't signal onResponse on timeout);
that progress-stall is pre-existing and separate from this race fix.

https://claude.ai/code/session_01NM2TyJtosLdY5ycjyabSRS
2026-06-10 19:16:59 +00:00
Claude 7e7898bf77 refactor(clink): audit follow-ups — consistent error detail, non-null priceType, budget guard
From the audit of this session's changes:

- Error surfacing: the budget (WalletScreen) and offer/invoice card
  (InvoicePaymentDispatcher) paths now use DebitResponse.failureDetail() like the
  zap path, so a GFY code-5/code-4 surfaces its range/retry_after instead of just
  the bare error string.
- NOffer.priceType is now non-null: decode already defaults an absent TLV 3 to
  SPONTANEOUS, so the nullable type was misleading and the '?: SPONTANEOUS'
  fallbacks in ClinkOfferPreview were dead. Drops them and the now-redundant
  always-emit-TLV3 test (covered by the spontaneous round-trip).
- WalletViewModel.requestDebitBudget catches the budget-validation
  IllegalArgumentException so a malformed frequency dismisses the dialog instead
  of hanging the spinner.
- Document why ClinkDebitPayer signs with the persistent account key (stable
  identity for budgets) while ClinkOfferPayer uses an ephemeral key.

https://claude.ai/code/session_01NM2TyJtosLdY5ycjyabSRS
2026-06-10 18:55:57 +00:00
David KasparandGitHub 08f6c63d02 Merge pull request #3172 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2026-06-10 20:50:49 +02:00
Crowdin Bot b94803b701 New Crowdin translations by GitHub Action 2026-06-10 18:49:25 +00:00
davotoulaandClaude Opus 4.8 9fd53b6461 feat: add cs, de, sv, pt-BR translations for chat history and reply search strings
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-10 20:45:28 +02:00
David KasparandGitHub 84c1f4f533 Merge pull request #3169 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2026-06-10 20:38:46 +02:00
Crowdin Bot e1835e5584 New Crowdin translations by GitHub Action 2026-06-10 18:38:37 +00:00
Vitor PamplonaandGitHub 1d98583f28 Merge pull request #3171 from vitorpamplona/claude/intelligent-newton-8qgvo8
Extract Marmot group message composer to ViewModel
2026-06-10 14:35:50 -04:00
davotoula 572f4005e1 test: guard kind-range vs class-hierarchy invariant in EventFactory
Sweeps every typed kind: addressable kinds (30000..39999) must read
their d tag, plain replaceables (10000..19999, 0, 3) must ignore stray
ones — the invariant the kind-34235/34236 fix restores.
2026-06-10 20:33:22 +02:00
davotoula d90574c4e9 refactor: rename ReplaceableVideoEvent to AddressableVideoEvent 2026-06-10 20:32:57 +02:00
Vitor PamplonaandGitHub 3393469a41 Merge pull request #3170 from vitorpamplona/claude/upbeat-einstein-fn5a3j
fix: treat kind-9 ChatEvent as a chat kind for inline chat-style quotes
2026-06-10 14:30:51 -04:00
Claude 33f1d20a21 fix(clink): mirror NWC's split progress on the debit zap rail
Advance half the per-payable progress on dispatch and the other half when the
async debit response arrives, exactly like payViaNWC, instead of jumping the
full share on dispatch.

https://claude.ai/code/session_01NM2TyJtosLdY5ycjyabSRS
2026-06-10 18:29:46 +00:00
Claude 51eecbc557 Merge remote-tracking branch 'origin/main' into claude/intelligent-newton-8qgvo8 2026-06-10 18:26:08 +00:00
Claude 908bc58190 test: lock in reorder-only semantics of mention priority ranking
Extracts the priority sort into rankPriorityFirst() and covers: priority
users move to the top, stable order within both groups, no injection of
non-matching priority keys, and untouched list when priority is empty.
2026-06-10 18:25:57 +00:00
Vitor PamplonaandGitHub 33f305b58e Merge pull request #3167 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2026-06-10 14:23:36 -04:00
Claude 14cb06d081 fix: treat kind-9 ChatEvent as a chat kind for inline chat-style quotes
The MLS/Marmot inner message kind was missing from isChatEvent, so a
chat message quoted inside an MLS chatroom message still rendered as the
default NoteCompose card instead of the chat reply design.

https://claude.ai/code/session_01DSQW7kku5cGEL36icXg6BC
2026-06-10 18:23:15 +00:00
Vitor PamplonaandGitHub 5e6c8ce641 Merge pull request #3168 from vitorpamplona/claude/upbeat-einstein-fn5a3j
Add InlineQuoteRenderer strategy for customizing quoted notes
2026-06-10 14:23:08 -04:00
Claude 15b12e7f13 refactor(clink): make the debit zap rail fire-and-forget like NWC
payViaClinkDebit blocked the zap on the debit service's res:ok/GFY reply (up to
30s) before reporting a result. Mirror the NWC rail instead: dispatch the debit
on the account scope and report each payable paid optimistically so the zap UI
completes promptly; a GFY/failure (or no reply) surfaces asynchronously through
onError rather than blocking. The programmatic App Functions debit path is
unchanged (it still awaits the real result).

https://claude.ai/code/session_01NM2TyJtosLdY5ycjyabSRS
2026-06-10 17:54:09 +00:00
Claude fde5818044 refactor: extract MarmotNewMessageViewModel for the MLS composer
Moves the Marmot composer's inline state (message TextFieldState,
reply state, upload state, @-mention suggestion wiring, send) into a
ViewModel mirroring ChatNewMessageViewModel / ChannelNewMessageViewModel /
NestNewMessageViewModel, so all four chat types share the same
init/load structure. No behavior change.
2026-06-10 17:43:30 +00:00
Claude 6f79779885 feat: mark conversation participants with an 'In this chat' chip in mention suggestions
Renders a small chip on suggestion rows whose pubkey is in the
suggestion state's priorityPubkeys set, unless the caller supplies
its own trailingContent. Priority keys only reorder and label the
users that already matched the search — they never inject results.
2026-06-10 17:40:52 +00:00
Claude 61387ba12c docs(clink): record interop review + spec-conformance pass results
https://claude.ai/code/session_01NM2TyJtosLdY5ycjyabSRS
2026-06-10 17:40:29 +00:00
Claude c5147b1203 fix: treat kind-9 ChatEvent as a chat kind for inline chat-style quotes
The MLS/Marmot inner message kind was missing from isChatEvent, so a
chat message quoted inside an MLS chatroom message still rendered as the
default NoteCompose card instead of the chat reply design.

https://claude.ai/code/session_01DSQW7kku5cGEL36icXg6BC
2026-06-10 17:37:33 +00:00
Claude b79ab1214c refactor: address review findings on mention tagging and suggestions
- Move NewMessageTagger from the Marmot composer into
  AccountViewModel.sendMarmotGroupMessage so every send path gets
  mention rewriting + p-tagging, not just the chat composer.
- Pass the parent's MarmotGroupChatroom into the composer instead of
  re-fetching it from the group list.
- Bound the public-channel participant scan with a one-month cutoff
  (matches the recency-cutoff convention in ChannelObservers).
- Simplify the nests participant-set construction.
2026-06-10 17:24:27 +00:00
Claude f4e0bcf73d fix(clink): spec-conformance hardening (k1 length, frequency, description, GFY detail)
Follow-ups from the line-by-line spec audit, scoped to the consume-only client:

- NDebit.parse rejects a TLV-3 session id that isn't exactly 32 bytes (64 hex),
  per clink-debits: a wrong-length k1 is a malformed session pointer.
- DebitClient.requestBudget validates frequency.unit is one of day/week/month
  (DebitFrequency.VALID_UNITS) instead of sending a unit a node service will GFY.
- OfferClient caps the invoice description at 100 chars per clink-offers.
- DebitResponse.failureDetail() composes the GFY error with its actionable extra
  (allowed range for code 5, retry_after for code 4); the debit zap path now
  surfaces that instead of the bare error string.

Adds regression tests for each (malformed-k1 rejection, invalid-unit throw,
description truncation, failureDetail range/retry_after).

https://claude.ai/code/session_01NM2TyJtosLdY5ycjyabSRS
2026-06-10 16:44:00 +00:00
Claude 429aa4f4a6 fix: skip chat reply row when the reply is already cited inline in the message
In MLS kind-9 chats Note.replyTo keeps both replies and quotes (e and q
tags), so a message quoting another chat message rendered it twice: once
in the reply row and once at its inline nostr: mention. Render the reply
row only when the target is not cited in the content; the inline quote
renderer already draws it in place.

https://claude.ai/code/session_01DSQW7kku5cGEL36icXg6BC
2026-06-10 16:37:56 +00:00
Crowdin Bot 0800eecbe0 New Crowdin translations by GitHub Action 2026-06-10 16:36:02 +00:00
Claude bf0fa0c57e feat: rank conversation participants first in @-mention suggestions
Adds an optional priorityPubkeys supplier to UserSuggestionState that
stable-sorts search results so the current conversation's participants
appear before network-wide matches (ranking only, never filters).

Wired per chat context:
- MLS/Marmot groups: live MLS member list
- NIP-17 DMs/groups: the room's users
- Public chats (NIP-28): authors who have posted in the channel
- Nests audio rooms: MeetingSpaceEvent participants + host

https://claude.ai/code/session_013NWdjCSegsf2FYSPPANX3n
2026-06-10 16:34:09 +00:00