#Amethst v1.15.0: Marmot Updates, a Advanced Search Box, and Books, DVM Updates Highlights: - Rebuilds **Marmot** onto the adopted spec — Amethyst groups now interoperate with White Noise, in both directions. - Makes the **search box's tokens real filters**, locally and on relays, and puts Android and Desktop on one pipeline. - Reads **publications, wikis, citations, library records and ratings**. - Adds **NIP-A3 payment targets** to the zap picker, and stops NWC failing in silence. - Shows which **DVMs are alive** before you pick one. - Brings **NIP-53 live streaming** to Desktop — discover, watch and chat. - Stops fresh installs **stranding on a Tor bootstrap**. ## New Features ### Marmot Groups Catch Up With the Spec Marmot deprecated the MIP documents in July and MDK followed, which left our implementation — shipped in v1.09.0 — no longer a valid Marmot client under either profile the current spec defines. This release moves it onto the adopted "current profile" and proves it against the reference. - Implements the MLS extensions draft's `app_data_dictionary`, the account identity proof v2 that makes a group classifiable at all, and the six group components that replace the single packed `marmot_group_data` blob. - Replaces the old same-epoch tie-break — lowest `created_at`, then event id, both of which a sender picks — with the spec's convergence: candidate branches derived by replaying MLS bytes, a bounded pass, and a six-step comparison over authenticated values only. - Enforces publish-before-apply. A local commit is prepared on a clone and becomes canonical only once a relay in the group's own scope acknowledges it, so a failed publish can no longer fork this client away from the group. - Fixes the defects that made us unaddressable: no account could mint a KeyPackage at all; KeyPackages were published as bare bytes instead of a framed MLSMessage; a Commit rebuilt our own leaf from defaults, silently demoting the group creator and making every invite we sent invisible; and a last-resort KeyPackage was discarded after one Welcome, so only the first invite anyone ever sent us worked. - Fixes two MLS defects that meant nobody but a group's creator could invite into a group of three or more: a joiner was never given its Welcome path secret, and an UpdatePath was decrypted at our own leaf rather than at the node we hold a key for. - Makes a departure actually take effect. A leaver's `SelfRemove` was committed and then dropped, so the group believed they were gone while they stayed in the tree holding its keys. - Adds group disbanding, with the whole Commit shape the spec fixes, and an admin-only action behind its own confirmation. - Sends attachments in the adopted encrypted-media v2 shape. Every image Amethyst sent into a group was invisible to every other implementation; an admin can now also switch a group over to encrypted attachments. - Honours disappearing messages, pinned to the epoch that delivered each message rather than to the current setting, with a retention picker at group creation. - Renders message edits and group system rows, and draws a system row only when this client derived it — a peer's assertion of "X removed Y" is no longer drawn as history. - Adds several members in one commit, the way MDK does, instead of one commit per invitee. - Adds a group avatar behind a plain https link, with the canonical URL rules the component requires and an SSRF guard at the renderer. - Adds push token gossip in the shape the spec adopted, with the kind-451 owner proof, so relaying someone's record cannot repoint, re-sign or restamp it. - Adds agent text stream previews over the repo's own QUIC stack, in both directions, with certificate pinning instead of blind trust. - Runs the reference implementation's own fixtures, its portable scenario vectors, and a headless interop harness against MDK 0.9.21 — 29 of 29, both directions, including media in both. ### Search - Ports the vespa-relay search field's token language: `from:`/`to:`, `since:`/`until:`, `#tag`, `label:`, `group:`, `kind:`, `lang:`, `domain:`, NIP-73 scopes, `-exclusions` and quoted phrases, drawn as chips over ordinary editable text. - Runs them. Android handed the raw box — chips and all — to both the relay's NIP-50 `search` and the local scan, so `from:npub1… bitcoin` asked relays for the literal text of its own tokens and a `kind:` chip narrowed nothing. - Puts both front ends on one `SearchPipeline`, so a caller cannot skip post-filters or ranking. Android had never applied `-term` exclusions or the reply/media pseudo-kinds, and Relevance sorted by date. - Opens a picker under a half-written `kind:`, and gives the people picker the composer's own — NIP-05 resolution, relay lookups and follow ranking included. - Names what a chip stands for: a group's name instead of its opaque id, a city instead of a geohash. - Seeds the box with the filter of the screen it was opened from, across 17 more feeds, and remembers what you searched for on Android. - Says what it is waiting on: the spinner now turns while a relay still owes an EOSE, and tapping it lists the relays outstanding. - Answers with one kind window derived from Quartz instead of three hand-kept lists — the audit found fifteen searchable, renderable kinds the search had never asked for, pictures and every video kind among them. - Hides deletion (kind 5) and vanish (kind 62) events from results. ### Reading: Publications, Library, Wiki and Ratings - Gives a kind-30040 publication a table of contents you can read from, listing sections by `a` **and** `e` tag and naming each row from the index alone. - Renders kind-30041 sections: AsciiDoc converted to the same CommonMark renderer long-form uses, with wikilinks resolved to nevents and naddrs. - Gives a chapter a crumb back to its book and a pager through the book. - Parses and renders the library kinds — 31/32/33 citations, 30045 directories, 30142 learning resources and 32176 Blossom piece indexes — and shows a learning resource's attached PDF inline instead of as a blue link. - Parses and renders the NIP-54 wiki kinds (818 merge requests, 819 acceptances, 30819 redirects) and kind-17 external reactions. - Parses kind-34259 entity ratings and kind-31987 relay reviews, and rebuilds the rating card around cover art and stars that actually fill. - Renders kind 0 as a profile card, in the feed and in thread view, instead of raw JSON. - Links each Birdex species to its Wikidata entry. ### Payments and Zaps - Offers a NIP-A3 pay-to hand-off in the zap picker, gated on an installed app actually resolving the URI and wearing that app's own icon. - Hides on-chain zap amounts the wallet cannot fund, checked against the exact coin selection the send path uses. - Names the payee on outgoing NWC transactions (NWC-06), and only claims a binding for a zap request the provider accepted. - Never lets a NIP-47 refusal or timeout reach you as silence — a wallet answering `QUOTA_EXCEEDED` or `RESTRICTED` showed nothing at all. - Stops sending optional NIP-47 params as `null`, which some wallets refuse, and stops downgrading to NIP-04 encryption on a cold info cache. - Adds a Block Relay button to the relay NOTIFY payment prompt, so a paid relay asking on every reconnect can be answered once. ### DVM Liveness - Adds DVM heartbeats (kind 11998), hides DVMs whose last beat is older than 15 minutes from Discover, and marks the rest with an offline dot or banner. - Fetches beats from each DVM's own outbox relays, not only the discovery selection, so a live DVM your relays do not carry stops looking dead. ### Relay Login - Makes the prompt's "remember" switch mean the same thing for both answers. It was read only by the Log in button, so refusing with it on wrote nothing and the same dialog came back on the next reconnect. - Relabels the buttons to the answer they give, and adds "Never, all relays" opposite "Always, all relays", both behind one confirmation that names the consequence. - Stops an account-wide answer being swallowed when the confirmation outlives the prompt's 60-second window, and answers the prompts queued behind it. ### Everything Else - Shares your screen in a call, on Android. - Opens a real file picker for `<input type="file">` — it was a silent no-op in the in-app browser, in nSites and in napplets — and offers the camera where a mobile browser would. - Exposes NIP-44 encrypt and decrypt on the injected `window.nostr`, so a web app logging in with Amethyst can seal a NIP-59 gift wrap. - Emits the uppercase `D` day-index tags on kind 31923, so calendar events are discoverable by date. - Notifies on the rest of NIP-34: PR replies, merges, closes, reopens and drafts. A merged PR used to arrive on the device and go nowhere. - Remembers which side-menu sections you collapsed (#4010). - Leaves the always-on notification card at the bare relay count, with the per-relay breakdown behind a "Show details" action. - Lifts the 3-relay cap on Concord invite links, which blew up the invite button for any community with more. - Saves videos to `Movies/` instead of `Pictures/` (#4009). ## Performance - Draws Amethyst's own icons from a generated font rather than rasterising an ImageVector per card: frame duration P90 -10.7%, overrun P90 -17.4%. - Defers feed animation transitions until there is something to animate: overrun P90 -19.4%. - Stops copying whole GIFs into RAM before decoding them. One 69.8 MB GIF cost 66 MB of heap plus 66 MB of native memory and ~700 ms of pure copying, on every scroll back into view. - Restores the `ImageDecoder` path for still images, which the same identity check had cost every network image in the app. - Makes the concurrent-fetch de-dupe actually work — it was rebuilt per request, so it never saw a second caller — rate-limits the cache reconciler to once a day, and reclaims cache files orphaned by a process death. - Stops blocking an OkHttp thread to sign Blossom read-auth, which could occupy every per-host slot on a gated host, and collapses a cold burst onto one signature. - Asks for many addresses in one relay filter instead of one filter each: opening a 240-section publication went from a black screen for ~2 minutes at 120% CPU to first paint at ~22s. - Rewrites Curve25519 field arithmetic on 10 limbs of radix 2^25.5 and runs the ladder without allocating: x25519 541us → 121us, and against MDK `create_group/1` goes from 4.7x slower to 1.5x while `send_app_message` is 6.5x faster. - Stops `FilterMatcher` allocating once per event — three separate ways, on a path that runs tens of thousands of times per keystroke during a search. - Adds an allocation-free read path for NIP-50 indexable text to all 133 searchable kinds, pinned byte-for-byte against the write path. - Shares one IME-inset holder per window instead of one per call site. - Prices the NIP-19 bracket peel and makes it free. - Defers log message construction to the lambda overload, including the loops the first sweep's line-anchored greps missed. - Bounds the route-text scan by the budget rather than the input. - Bounds in-flight on-device AI work and stops blocking IO threads on it. - Single-flights the LNURL endpoint resolver: a zap-receipt burst was making ~20 requests to one provider per user action. ## Improvements and Bug fixes - Stops a fresh install stranding on a Tor bootstrap. The watchdog fired once per `Connecting` span and a timeout produces no status change, so after exactly two attempts it went silent — permanently. Arti now bootstraps on demand instead of blocking the whole directory download behind its lifecycle lock. - Classifies the app's stand-in relays like the user's own until their lists arrive, so a new install stops routing everything over Tor by construction: first relay socket moved from login+5.9s to login+1.2s, and events ingested by the 20s census from 0–2,590 to 3,719–6,051. - Only substitutes default relays when there is no event, not when the list is empty — publishing an empty search or inbox list is a choice, and the app was overriding it. - Stops relay list updates wiping the public entries another client wrote (#4075). - Recovers IME padding when Compose's insets listener freezes. A back gesture could leave a keyboard-sized gap app-wide, permanently, until the activity was killed; the correction is now 120ms rather than 400ms. - Keeps `TextFieldState` writes on the UI thread, fixing a crash inside Compose's undo bookkeeping on the keyboard's own callback. - Follows the phone's light/dark setting on the launch splash, and carries a pinned LIGHT/DARK choice into it through the per-application night mode. - Uses the HLS master playlist and its full quality ladder, sizes the ladder to the player rather than to a fixed policy, caps it on metered connections, and fixes a picture-in-picture race. - Caps MediaSession artwork so the platform never re-scales it — a crash on ROMs that recycle the source bitmap. - Stops cancelling ML Kit GenAI inference futures, which killed the process from a thread the app does not own. The composer cancelled one on every keystroke. - Makes the full-screen viewer follow the system bars instead of reserving the strip they would occupy, and brings the PDF viewer's chrome to parity. - Caps navigation route text arguments. Sending a crash report to the developer crashed the app: ICU's regex stack overflows at ~100,000 characters and reports "no match" rather than an error. - Narrows the `FileProvider` external root to the app-specific directory. - Stops deleted lists showing their UUID in the feed filter picker (#3949). - Restores GIF insertion on the comment reply screens, where a keyboard GIF did nothing and a shared one threw away the reply in progress, and wires it into four more composers. - Keeps calls alive when the system destroys `MainActivity`, releases the screen-capture Surface instead of leaking one per session, and stops reopening the camera during teardown. - Stops reporting coroutine cancellation as a signer failure, which surfaced as a "signer not found" toast on essentially every fast settings toggle. - Stops a picker's scrolling from dragging the top and bottom bars with it. - Tints the un-boosted repost icon like the rest of the action row, and fixes the liked and reposted colours. - Stops labelling the mixed media feed "Shorts" — two different destinations in the bottom-bar picker carried the same label. - Converts Android string escaping to Compose escaping inside the Crowdin sync, and gates it in CI, after three syncs in a row reintroduced ~2,900 escaped apostrophes that render with a visible backslash. ## Desktop - Adds NIP-53 live streaming: a "Live now" section in Discover with ranking and search, a per-column live bar on the Following and Global feeds, and a watch overlay with player, live chat and live-mode controls, openable from anywhere. - Routes kinds 30311 and 1311 into the desktop cache, which had no channel store at all, with a 500-message chat cap. - Shares the unified search pipeline, and gives the spotlight the same people picker — typing `from:` there opened nothing. - Surfaces macOS notification-permission errors and times the request out, rather than parking forever on a banner the user missed. ## Cli - Redesigns `amy status` around who is signed in and what they saved, adding relay config, follows, Marmot messages, Concord communities and the operator key — and fixing a store report that read 0 events on the default backend. - Drives the Marmot work end to end: current-profile group creation, retention, media policy and transfer, disband, and `amy marmot stream`. ## Quartz - Adds the adopted Marmot profile: the app-data dictionary carrier, account identity proof v2, the six group components, the lifecycle state machine, branch selection, the bounded convergence pass, the candidate-graph builder, encrypted media v2, the kind-451 push owner proof and the agent-text-stream codecs. - Replaces `org.json` with `kotlinx.serialization` across the remaining sources. - Backs linuxX64's `LargeCache` with a striped-lock hash table instead of copy-on-write, which was O(n) per put and not thread-safe. Filling 100k entries goes from 70ms to 15ms with 1 GC instead of 24 — and the whole linuxX64 suite now passes (3,495 tests, from 78 failures), so the CI leg runs all of it. - Matches `java.net.URLEncoder` on native and drops the `urlencoder` dependency. The two implemented different specifications, so Android and iOS emitted different bytes for the same magnet link or NWC deep link. - Routes the roles the NIP-50 search extractor was stranding in the body tier: 19 kinds gain a branch, including the marketplace and Podcasting 2.0 families whose titles could never reach a title band. - Gives the Trusted List member score a 0–100 scale, reads the generic Trusted List entry in a NIP-85 Treasure Map, carries Map entries in both halves of the 10040, and indexes Trusted List titles for NIP-50 search. - Holds a transport failure as provisional for one retry when a relay drops the socket between our EVENT and its OK, and restores pool membership first so the retry dials something. - Waits out the second instead of stamping `created_at` in the future when superseding a replaceable event. - Adds `DvmHeartbeatEvent` (kind 11998) and the publication, library, wiki and rating parsers. ## Build & Documentation - Bumps Kotlin to 2.4.20, Compose Multiplatform to 1.12.0 and AGP to 9.4.0, plus the rest of the version catalog. - Executes the commons migration sweep: 38 relayClient files, the OkHttp client stack, 37 model and service singles, the top-nav feed datasource layer and the search history move to `commons`; the 12 typealias shims are deleted and their 1,165 imports repointed; 25 files are promoted from `jvmAndroid` to `commonMain` after a per-file concurrency review. - Migrates 2,565 string keys — about 105,800 locale entries — to the shared Compose resource catalog, and gates the orphaned-translation and escaping traps pre-push and in CI. - Publishes desktop test reports when the desktop job fails, which was the only test-running job with none. - Renames the `geode` Homebrew formula to `geode-relay`, since `geode` is permanently reserved for Apache Geode, and fixes a style violation in both formulae. - Adds `marmotBench`, a head-to-head against MDK's own benchmark, reporting bytes allocated per operation alongside latency. ## Contributors - @Vitor Pamplona - @⚡ Dee Kay ⚡ - @Minced Beef Energy - @Don't Believe The Vibe 🌱🍋🍊 - @greenart7c3 - @narmic - @mstrofnone ⚡🥜 ## Translations - Dutch by @Bartus - Hindi by @राज - Hungarian by @summoner - Polish by @Polak - Slovenian by @StellarStoic

Replies (2)

Default avatar
Showtime 9 hours ago
@NostrFRA @npub1e6m7...me3z #maj #app #NostrFr #Showtime On ne peut pas dire que tout n’est documenté 😅. Êtes-vous satisfait de ce client. Ne devient-il pas un usine à gaz ? Les nouveautés : #Amethst v1.15.0 : mises à jour de Marmot, un champ de recherche avancée, et mises à jour concernant les livres et DVM Points forts : - Réimplémentation de **Marmot** selon la spécification adoptée — les groupes Amethyst interagissent désormais avec White Noise, dans les deux sens. - Transforme les **jetons du champ de recherche en véritables filtres**, en local et sur les relais, et regroupe Android et la version bureau dans un même pipeline. - Lit les **publications, wikis, citations, notices bibliographiques et évaluations**. - Ajoute les **cibles de paiement NIP-A3** au sélecteur de zap et empêche NWC de tomber en échec sans avertissement. - Indique quels **DVM sont actifs** avant que vous n’en choisissiez un. - Apporte le **streaming en direct NIP-53** sur ordinateur de bureau — découvrez, regardez et discutez. - Empêche les nouvelles installations de **se retrouver bloquées lors du démarrage de Tor**. ## Nouvelles fonctionnalités ### Les groupes Marmot se mettent au diapason de la spécification Marmot a déprécié les documents MIP en juillet, suivi par MDK, ce qui a rendu notre implémentation — fournie dans la version 1.09.0 — incompatible avec les profils définis par la spécification actuelle. Cette version la fait passer au « profil actuel » adopté et valide sa conformité par rapport à la référence. - Implémente le `app_data_dictionary` du projet d’extensions MLS, la preuve d’identité de compte v2 qui rend un groupe classifiable, ainsi que les six composants de groupe qui remplacent le bloc `marmot_group_data` unique et compressé. - Remplace l’ancien critère de départage en cas d’époche identique — `created_at` le plus bas, puis identifiant d’événement, tous deux choisis par l’expéditeur — par la convergence prévue par la spécification : des branches candidates dérivées par relecture des octets MLS, un passage borné et une comparaison en six étapes portant uniquement sur les valeurs authentifiées. - Impose la règle « publier avant d’appliquer ». Une validation locale est préparée sur un clone et ne devient canonique qu’une fois qu’un relais appartenant à la portée du groupe l’a validé, de sorte qu’un échec de publication ne peut plus entraîner la séparation de ce client du groupe. - Corrige les défauts qui nous rendaient inaccessibles : aucun compte ne pouvait créer de KeyPackage ; les KeyPackages étaient publiés sous forme d’octets bruts au lieu d’un MLSMessage encadré ; un Commit reconstruisait notre propre feuille à partir des valeurs par défaut, rétrogradant silencieusement le créateur du groupe et rendant invisibles toutes les invitations que nous envoyions ; et un KeyPackage de dernier recours était écarté après un seul « Welcome », de sorte que seule la première invitation que quiconque nous envoyait fonctionnait. - Corrige deux défauts MLS qui empêchaient quiconque, à l’exception du créateur d’un groupe, d’inviter des membres dans un groupe de trois personnes ou plus : un nouveau membre ne recevait jamais son secret de chemin « Welcome », et un « UpdatePath » était déchiffré au niveau de notre propre feuille plutôt qu’au niveau du nœud pour lequel nous détenons une clé. - Permet à un départ de prendre réellement effet. La commande `SelfRemove` d’une personne quittant le groupe était validée puis ignorée, de sorte que le groupe croyait qu’elle était partie alors qu’elle restait dans l’ arborescence détenant ses clés. - Ajoute la dissolution de groupe, avec l’ensemble des modifications de validation prévues par la spécification, et une action réservée à l'administrateur, nécessitant une confirmation spécifique. - Envoie les pièces jointes au format « encrypted-media v2 » adopté. Auparavant, toutes les images envoyées par Amethyst dans un groupe étaient invisibles pour toutes les autres implémentations ; un administrateur peut désormais également activer les pièces jointes chiffrées pour un groupe. - Prend en charge les messages éphémères, liés à l'époque de livraison de chaque message plutôt qu'au paramètre actuel, avec un sélecteur de durée de conservation lors de la création du groupe. - Affiche les modifications de messages et les lignes système du groupe, et n’affiche une ligne système que lorsque ce client l’a générée — l’affirmation d’un pair selon laquelle « X a supprimé Y » n’apparaît plus dans l’historique. - Ajoute plusieurs membres en une seule validation, à la manière de MDK, au lieu d’une validation par invité. - Ajoute un avatar de groupe derrière un simple lien https, avec les règles d’URL canoniques requises par le composant et une protection SSRF au niveau du moteur de rendu. - Ajoute la propagation des jetons push conformément à la spécification adoptée, avec la preuve de propriété de type 451, de sorte que la retransmission de l’enregistrement d’une personne ne puisse pas le rediriger, le resigner ou le réhorodater. - Ajoute des aperçus du flux de texte de l'agent via la pile QUIC propre au dépôt, dans les deux sens, avec un « certificate pinning » au lieu d'une confiance aveugle. - Exécute les fixtures propres à l'implémentation de référence, ses vecteurs de scénarios portables et un harnais d'interopérabilité sans interface graphique contre MDK 0.9.21 — 29 sur 29, dans les deux sens, y compris les médias dans les deux cas. La suite concernant les autres améliorations, nous vous laissons les découvrir directement sous la publication ci-dessous. Les contributeurs de ce projet sont : - @Vitor Pamplona - @⚡ Dee Kay ⚡ - @Minced Beef Energy - @Don't Believe The Vibe 🌱🍋🍊 - @greenart7c3 - @narmic - @mstrofnone ⚡🥜 Les traductions par : - Néerlandais par @Bartus - Hindi par @राज - Hongrois par @summoner - Polonais par @Polak - Slovène par @StellarStoic Vivement qu’il puisse y avoir d’autres langues pour ce projet d’envergure. Merci à vous les dev. Bon week-end. View quoted note →