# v1.18.0: Amethyst Desktop, Web of Trust, Privacy Lock, and a Deck

Highlights:

- Ships **Mobile UI for the Desktop**: the desktop app now runs the same screens
  and navigation as Android, and the legacy desktop app is gone.
- Adds a **Web of Trust** network from your NIP-85 provider. Strangers' DMs go
  to New Requests, and Curated notifications and replies leave them out.
- Adds a **Privacy lock** for the whole app, your messages or your wallet,
  with biometrics on Android and a password on desktop.
- Adds **Custom feeds** built from people, hashtags, kinds and relays, and an
  opt-in **Deck** of columns for wide windows.
- Rewrites **web-app signing prompts** to say what each request does, and
  stops one stuck prompt from blocking every later signature.
- Lets you **drag the panes** in wide windows, and opens every chat type
  beside its list.
- Redesigns **My Fitness** around weekly goals, with streaks, history and
  charts.
- Makes the **Messages toggles** stop loading NIP-04 or NIP-17, instead of
  only hiding them.
- Renders **NIP-82 software releases** with their app, release notes, and
  downloads for your device first.

## New Features

### Privacy Lock

- Adds a Privacy lock settings screen with three switches, all off by default:
  lock the entire app, lock messages, lock the wallet. Plus an idle timeout.
- Unlocks with biometrics or the device credential on Android, and with an
  app password on desktop.
- Locks you out for 30 s after five wrong passwords, doubling up to
  5 minutes, and keeps the lockout across restarts.
- Asks for the credential before a lock is turned off or its timeout made
  longer.
- Hides message text in notifications while the messages or app lock is on,
  and guards the secret-key backup behind any lock.
- Locks the Marmot and Cordn group screens, drafts, and the payment and mint
  top-up screens too.

### Custom Feeds and Deck

- Adds custom feeds: pick people, hashtags, kinds and relays, minus hidden
  people and keywords. They show in the Home feed picker under "Your feeds",
  with a manage screen and an editor.
- Adds an opt-in Deck (Settings › App › Deck) for windows 1,200 dp and wider,
  in place of the notification panel.
- Makes every Deck column any screen, with its own back stack. Columns can be
  added, moved, resized, reset and closed, across up to nine workspaces.
- Adds Deck shortcuts on desktop: Ctrl/Cmd+T, +W, +Shift+PageUp/PageDown,
  +Alt+1–9 and +Shift+S, plus a Deck menu on macOS.
- Imports the legacy desktop app's feeds, workspaces and lock settings.

### Web of Trust

- Downloads your NIP-85 rank provider's trust cards into a compact index on
  the device, and keeps it current with small, cheap updates.
- Treats people below a minimum trust score as strangers. Their DMs go to New
  Requests, Curated notifications drop them (in-app and push, zaps exempt),
  and their thread replies start collapsed. Follows always pass.
- Adds a Web of Trust settings screen: the network's size over a histogram of
  ranks, a minimum-score slider with a live count, and sync status.
- Sets up Brainstorm's GrapeRank with a guided sign-up, or any NIP-85 provider
  by its key and relay.
- Reads rank and follower badges from the index, without a subscription per
  profile.
- Shows on a profile whether the person is in your network, with their score
  or "You follow them". Tapping it opens the Web of Trust settings.
- Collapses runs of public-chat messages from people outside your network
  into one "3 messages from outside your network · Show" row, in NIP-28,
  live-stream, ephemeral and relay-group chats.
- Orders people search, mentions and people lists by follows, then trust
  score, then everyone else.
- Resumes a cut-off trust network download instead of starting over.
- Lets you copy another person's trust view, or enter a provider key with its
  tags as chips, under "Set up by hand".
- Opens the provider's website in Amethyst's browser, signed in with this
  account.
- Adds the Brainstorm relay to the default search and indexer relays.

### Signing

- Times out a web-app signing prompt after 120 s. A prompt left behind Home
  used to block every later request from every web app.
- Brings a pending prompt back when Amethyst returns to the foreground.
- Shows what each request does: the private message inside a seal, the relay
  of a relay login, the method and URL of a NIP-98 request, and the verb, file
  and server of a Blossom upload or delete.
- Previews a profile edit as the new profile, with "Changes your bio ·
  Removes your lightning address" lines.
- Summarizes follow, mute, deletion and report changes in one line, and shows
  reports as report cards.
- Asks before signing a report under the Reasonable trust level, and asks
  once, not twice, in Paranoid mode.
- Warns when a prompt has been open a minute or more that the app may have
  stopped waiting.
- Fixes Connected Apps for web apps: Forget now forgets an app's decisions, a
  new trust level takes effect, and an app no longer shows once per account.
  A forgotten app could keep signing logins without a prompt.
- Only the buttons and Back decide a prompt now. A tap beside it no longer
  denies silently.
- Removes the 40-requests-per-10-seconds limit on Amethyst's own NIP-46
  signer for paired apps, and answers their requests in parallel, so busy
  clients are no longer refused. Unpaired keys get 10 requests a minute each,
  100 in total, and the rest are dropped unanswered.

### Messages

- Turns NIP-04 or NIP-17 loading, paging and subscriptions off when you turn
  its toggle off. With NIP-17 off, its rooms, composer and calls are gone, and
  the gift-wrap subscription asks only for what Marmot or Concord still need.
- Brings cached messages back when a protocol is turned on again.
- Pages NIP-17 history one round at a time. A quiet chat offers Keep looking
  and Stop instead of walking every gift wrap you ever received, and names the
  relays that didn't answer.
- Pages NIP-17 history from your DM relay list only, 500 gift wraps at a time.
- Pages history (messages, notifications, Cashu wallet) only for the account
  on screen. With several accounts logged in, a NIP-17 group showed only its
  last message.
- Keeps pinned conversations in the list when none of their messages is
  loaded yet.
- Opens every chat type beside its list on wide screens: Buzz, Marmot, Cordn,
  Concord, relay groups and location channels, like DMs.
- Softens the bubble colors: other people's bubbles are lighter in the light
  theme, and your own keep the accent's hue without the neon glare.
- Checks the stock Concord relays for invites every 15 minutes instead of
  keeping them open.

### Wide Windows

- Lets you drag the splitters between the drawer and the content, the content
  and the notification panel, and a chat list and its chat. Widths are saved
  per device.
- Opens a note's menu as a dropdown by its ⋮ button, with keyboard navigation
  and submenus, instead of a dialog in the middle of the screen.
- Wraps the composer's tools under the text instead of scrolling them
  sideways, with hover tooltips.
- Opens the account switcher as a menu from the docked drawer.
- Scrolls the feed with the mouse wheel over the empty gutters beside it.
- Adds a refresh button to the docked notification panel, and shows today's
  counts and the 7-day chart in it.
- Opens the drawer from a tab pager only after a 120 dp swipe or a fast
  flick, instead of on the first pixel, and keeps wheel and trackpad scrolls
  with the pager.
- Puts Navigate before You in the drawer when there is no bottom bar.

### My Fitness

- Redesigns My Fitness around weekly goals: rings for active minutes, workouts
  and active days, plus an optional weekly distance, each "on track" or
  "behind pace".
- Adds a goal editor. The default is 150 active minutes a week, the WHO
  guideline.
- Adds 12 weeks of weekly active time, a 18-week consistency heatmap with
  streaks, your busiest days and times, and the activity mix against the
  previous four weeks.
- Fetches your own published workouts, instead of only those another screen
  had loaded.
- Stops counting fasting, diet and meditation as active time.

### Composer and Uploads

- Adds a custom-emoji picker to the note, comment, DM and public-chat
  composers. It inserts your NIP-30 emoji as `:shortcode:`.
- Makes the image quality slider also set a maximum size: 640, 1280 or
  1920 px, then uncompressed. Posts start at 1280 px on phones, now at JPEG
  quality 85 instead of 75.
- Resizes photos on Android to exactly that size. A 12 MP photo used to go up
  at about 2016 px whatever the setting.
- Previews the first image compressed in the upload dialog, with its size and
  dimensions next to the original's. Tap it to compare both full screen at
  their real resolution, with zoom; tap again to flip between them.
- Deletes the temporary copies a compression leaves behind when the quality
  slider moves on before it finishes.
- Tags music uploads with their `format` (m4a, mp3, …), so other clients
  know the `.mp4` Blossom returns is audio.
- Adds imeta (hash, size, type, dimensions, blurhash) to images pasted as
  links in the note, comment, public message, classified and new group DM
  composers, so readers can lay them out before they load. Links without an
  image extension count when their preview is an image.
- Waits up to 3 s for a link just pasted before posting, so its imeta goes
  out with the post.
- Fetches a pasted link once for its preview card and its imeta.

### Feeds and Cards

- Renders NIP-82 software releases with the app's icon and name, markdown
  release notes and OS chips.
- Groups release downloads by OS, with the best match for this device on top,
  and splits pre-releases from the latest release.
- Trusts a release only from the app's publisher or its listed maintainers.
- Opens Zapstore apps instead of an empty group chat. Only group content
  follows its `h` tag to a NIP-29 group.
- Loads an app's releases on its own page, even when it is opened from a
  link, profile or mention.
- Fills bare app cards with their OSes, license and links, renders app
  descriptions like a note body with translation, and groups platform chips
  by OS.
- Adds cards for Kanban boards and cards, WalletScrutiny verdicts and Mostro
  instance terms.
- Adds a CI status badge and runs sheet, and cover notes, to NIP-34 git PRs,
  patches and issues.
- Finds Marmot key packages of White Noise users who publish only the legacy
  kind 443, so they can be invited.
- Shows the caption above the reactions on video and file cards.
- Plays an audio-only `.mp4` as audio, with its cover, by reading the file's
  track list. Blossom serves every M4A upload as `.mp4` / `video/mp4`, so
  these showed a black video box. A music track or podcast whose file is a
  real music video plays as video instead of inside the waveform strip.
- Shows the cover, title and player of audio tracks, and the player of audio
  headers that carry their file in a `url` tag.

## Performance

- Moves blocking and lock-touching work off `Dispatchers.Default`: external
  signer calls, proof-of-work mining, relay health saves, cache scans and
  cyberspace bag sweeps. One slow job no longer stalls feeds and relay
  verification behind it.

## Improvements and Bug fixes

- Shows each relay's median post, query and first-result times, and a
  "Slow N×" chip on a relay that lags the others.
- Hides the broadcast banner once your outbox relays accept a post, instead of
  waiting for every relay. It can always be dismissed, and an outbox failure
  stays with a Retry.
- Stops a dead outbox relay or a dropped connection from showing a post as
  failed.
- Counts every connected relay under a job in the always-on notification, and
  says "Connected to N relays" instead of "inbox relays".
- Asks before opening a scanned 64-character hex key as a profile, since it
  might be a private key.
- Completes multi-part QR codes from the camera and from images, waits for a
  tap when several codes are in view, and keeps zoom and torch when the camera
  reopens.
- Fixes the wallet invoice QR being clipped in wide windows.
- Converts GIFs to MP4 in comment, public message, classified and edit
  composers, which ignored the switch.
- Fixes uploads of keyboard GIFs and stickers failing with a permission error
  after the upload dialog had been open a while.
- Makes text fields in embedded web apps behave as in the browser: Enter
  sends, cleared fields stay cleared, chips, paste, maxlength, and Next on
  forms.
- Fixes the image zoom animation popping to full size instead of growing from
  the thumbnail with its rounded corners.
- Fixes a pasted `nevent` to a music track, or any addressable event, showing
  twice in search.
- Shows a reaction notification for a reaction to your reply. It was checked
  against the thread root's author.
- Shows nested NIP-22 replies in community feeds.
- Fixes zap splits without a relay being saved so that Amethyst and other
  clients read them as weight 0. Old splits still load.
- Counts on-chain and BOLT12 zaps toward your zap income only when you are the
  payee, and attaches a BOLT12 zap only to the live activity it names.
- Fixes hashtag feeds asking only the first relay.
- Shows "Messages" in the Messages top bar instead of the Amethyst logo.
- Fixes weekday labels on the notification chart after a daylight-saving
  change.
- Opens scanned links written in uppercase, and keeps a split nsec in an
  image from being listed piece by piece.
- Reports an LNURL server's error as "service unavailable" with its message.
- Stops a bare word in a relay hint turning into a fake relay.
- Fixes a StackOverflowError on reply cycles in threads, and a link detector
  that took 27 s on colon-heavy text.
- Fixes a second reaction with the same emoji missing from the reaction
  gallery.
- Keeps a feed's subscription when the app comes back right at the end of its
  background grace period.
- Opens a chat when you tap its name next to a community label.
- Fixes a crash on videos with side-loaded captions. They now show as
  subtitles, and formats no parser reads are left out.
- Fixes padding under the disappearing bars on My Fitness.
- Fixes the call timer showing blank in Polish, and the web-app profile-edit
  prompt not naming the changed fields in Polish.
- Shows errors with a severity icon, a selectable message and the stack trace
  behind Details. Messages queue instead of replacing each other.
- Moves the navigation to Navigation 3. Tabs keep their scroll position, and
  pinned web apps no longer share one saved screen.

## Desktop

- Runs the shared app: the same screens, navigation shell, login, account
  switcher and settings as Android. The legacy desktop app is deleted, and its
  accounts, feeds, deck and lock settings are imported.
- Gains what the legacy app lacked: zaps and the reactions row, chess, Git
  repositories, calendar RSVPs, post editing, markdown with inline media,
  Cordn groups, the audio-room list, and crash reports offered on the next
  start.
- Drops the hashtag-spam filter, the WoT badge, the live-now bar, the relay
  dashboard, the advanced search and the GIF search.
- Adds a 2560 px step to the image quality slider, and starts posts at
  1920 px.
- Keeps phone photos upright: compression applies the EXIF orientation, and
  the metadata strip keeps that one tag. Photos stored sideways were uploaded
  on their side.
- Strips metadata from photos with two EXIF blocks, as some editors save
  them. These were uploaded unstripped, GPS location included.
- Keeps the space for images and GIFs without a declared size while they
  load, and caches decoded GIF frames so a GIF shown again isn't downloaded or
  decoded again.
- Fixes tall photos spilling over the text and reactions around them.
- Draws QR codes with the shared renderer. Long payloads fall back to a lower
  error-correction level instead of crashing.
- Plays video like Android: autoplay muted, pause out of view, resume where it
  stopped, Android-style controls and a now-playing bar. Encrypted media plays,
  and Tor-routed videos stream through Tor.
- Renders music tracks, podcast episodes, audio tracks, audio headers, voice
  messages and audio in posts, which drew nothing before, with a waveform
  player that seeks on click. Podcast soundbites and chapters jump there.
- Plays audio linked in posts, including audio-only `.mp4`s, in the audio
  player instead of the video engine ("Can't play this video").
- Fits portrait videos, preloads the next videos in a feed, and no longer
  blinks to black when one starts.
- Zooms photos and videos from their card to full screen and back.
- Redesigns the now-playing bar with thin sliders and hours in long times, and
  keeps its stop button in narrow windows.
- Renders PDFs in notes, with a full-window reader that turns pages with the
  arrow keys.
- Shows OS notifications with the same title and text as Android, when
  previews are on, and skips muted authors.
- Replaces the bare system error box with a crash window that can send the
  report to the developers.
- Uses desktop text sizes, a lighter hover, and a menu bar only on macOS. The
  title bar blends into the app on macOS.
- Sizes the drawer's text and rows for the desktop, and keeps the photo
  viewer's close button clear of the macOS window buttons.
- Opens a notice instead of crashing on screens the desktop doesn't have yet,
  and hides their entry points, such as DM calls and the geocache Map tab.
- Drops the media setting's Wi-Fi choice, where it behaved like Always.
- Keeps the top bar on Scheduled posts when there is no post store, instead
  of a blank screen.
- Shows a text reply button on voice notes where recording isn't available.
- Brings a background window forward on the first click without pressing what
  is under the pointer, and stops hover while scrolling.
- Fixes the pull-to-refresh indicator staying half-pulled after a mouse-wheel
  scroll.
- Opens at a larger size and reopens where you left it, and never saves a
  window collapsed to a sliver.
- Names the app "Amethyst" in Linux docks and app switchers.
- Only hands http, https and lightning links to the OS.
- Keeps accounts, drafts and settings in the Flatpak across restarts. Every
  Flatpak shipped so far lost them.
- Renames the Flatpak app ID to `com.vitorpamplona.amethyst`, on runtime
  26.08. Remove the old `com.vitorpamplona.amethyst.Desktop` bundle and log
  in again (see `BUILDING.md`).
- Declares GStreamer as a dependency of the Linux `.deb` and `.rpm`.

## Cli

- Adds `amy notes show`, `thread`, `reply`, `quote`, `react` and `repost`,
  plus `delete`, `notifications` and `notes feed --hashtag`, on the app's own
  routing, tagging and thread code.
- Adds `amy trust setup`, `status`, `sync`, `check` and `copy` for the Web of
  Trust network.
- Reports `relay_errors` from `amy fetch`, and fails with `no_relay_served`
  when no relay served the request. It used to exit 0 with `count: 0`.
- Streams `amy fetch --paginate --limit 0` with flat memory, and logs every
  REQ with `--verbose`.
- Runs every `amy sync` direction through negentropy.
- Answers AUTH in `amy fetch`.
- Refuses reposts and quotes of NIP-29 group content, and sends group content
  only to its host relay.
- Shows a zap's sender only when the zap request proves it, so a spoofed
  zapper no longer shows.
- Fails interaction verbs with `read_only` on npub-only accounts, filters
  `amy notifications --type` at the relay, and applies deletions by address.
- Trims `notes post` text and rewrites bare npub/note words like the composer.
- Keeps amy's data files owner-only on Windows (NTFS ACLs), without eight
  warnings per file.

## Quartz

- Makes `fetchAllPages` hold up on production relays: it learns each relay's
  limit, drops repeated events, paces throttled relays, and walks each filter
  on its own cursor. Multi-filter walks lost most of their events before.
- Fixes `negentropySync` finishing "clean" on relays that truncate their set,
  and makes `fetchByIds` public.
- Returns null from `count` at once on relays without NIP-45.
- Fixes a WebSocket dial race that opened two connections per relay, and
  REQs sent while a WebSocket was still opening being dropped silently.
- Breaking: `fetchAllPages` drops `DEFAULT_PAGE_SIZE`; `pageSize` is now an
  optional cap. Adds `streamAllPagesFromPoolWithHooks` for walks too large to
  hold in memory.
- Keeps a relay's CLOSED or connection message in `PagedFetchResult.message`,
  and adds an `onRelayResult` callback to `fetchAllPagesFromPool`.
- Adds NIP-85 trust network sync and a compact index.
- Types the event kinds found by a census of 743 relays: Marmot 443, NIP-34
  cover notes and NIP-C1 CI, Mostro, RoboSats, Ditto, Zapstore identity
  proofs, Kanban, WalletScrutiny, NIP-4E key transfer, private note storage
  and more.
- Completes hint providers on 184 kinds and NIP-50 indexing on 72. Existing
  stores need `reindexFullTextSearch()`.
- Adds `RelayUrlNormalizer.normalizeHintOrNull`, a strict parser for relay
  hints from other clients.
- Names every tag relationship with an accessor, such as `mentions()`,
  `quotedEvents()` and `recipient()`.
- Writes the `a` and `f` tags NIP-82 releases require, and adds NIP-82 version
  ordering.
- Verifies Zapstore identity proofs (kind 30509) against an APK signing
  certificate (RSA, EC, Ed25519) on JVM and Android.
- Reads NIP-4E draft 10044 keys, which were always empty before, and adds
  `EncryptionKeyStore`. Parses NIP-69 bonds as decimals.
- Registers `FeedDefinitionEvent` (31890) and `EncryptionKeyListEvent` (10044)
  in `EventFactory`, and indexes an emoji pack's public shortcodes instead of
  its encrypted content.
- Breaking: adds abstract `BaseRepostEvent.originalAuthorKeys()` and
  `ZapReceiptEventInterface.zappedAddresses()`, and `DecoupledCipher` takes an
  `EncryptionKeyStore`.
- Shares secp256k1 across every target and one native crypto set across
  Apple and Linux, and implements `LargeSoftCache` on iOS.
- Adds a "One-shot Reads" section to `CLIENT.md` (`fetchAll`, `fetchFirst`,
  `fetchAllPages`, `count`), switches it to the Maven artifact and the bundled
  OkHttp transport, and ships an installable agent skill for projects that
  use Quartz (`quartz/agent-skill/`).
- Adds `AudioTrackEvent.title()`, `url()`, `image()` (with `mediaUrl()`,
  `coverUrl()`, `displayTitle()`), `AudioHeaderEvent.url()` / `mediaUrl()`,
  and a `format` parameter to `MusicTrackEvent.edit`.

## Build & Documentation

- Continues One UI: Navigation 3 with the navigation shell in `commonsUI`,
  more screens and ViewModels moved to `commons`/`commonsUI`, a headless
  `BaseAccountViewModel` with typed user notices, and feature-based ViewModel
  packages.
- Adds plans for iOS and web apps on the shared UI.
- Adds Navigation 3 1.1.2 and Apache PDFBox 3.0.8 (desktop only), both
  Apache-2.0, and removes `navigation-compose`.
- Turns on incremental compilation for the KMP modules: a one-line change in
  `commons` rebuilds in 9.5 s instead of 38.6 s.
- Builds zxing-cpp reproducibly from any NDK path, from a pinned commit.
- Requires a human to review the diff and test on a device before an AI
  contribution becomes a PR.
- Gives Claude Code and Codex the same rules, skills and pre-push checks:
  `AGENTS.md` holds the rules, `.agents/skills` links to `.claude/skills`, and
  the checks live in `tools/agent-hooks/`. A new check, run before every push
  and in CI, keeps them in sync.
- Cuts the agent skills from 33 to 14, each rewritten against the current
  code, so the list fits Codex's skill budget.
- Fixes the Compose string-escaping pre-push check, which never blocked a
  push.
- Documents Homebrew as autobumped, and verifies the cask instead of
  submitting it.
- Bumps `appCode` to 463.

## Contributors

- @npub1gcxzte5zlkncx26j68ez60fzkvtkm9e0vrwdcvsjakxf9mu9qewqlfnj5z
- @npub1e2yuky03caw4ke3zy68lg0fz3r4gkt94hx4fjmlelacyljgyk79svn3eef

## Translations

- Czech, German, Swedish, and Brazilian Portuguese by @npub1e2yuky03caw4ke3zy68lg0fz3r4gkt94hx4fjmlelacyljgyk79svn3eef
- Hindi by @npub1ww6huwu3xye6r05n3qkjeq62wds5pq0jswhl7uc59lchc0n0ns4sdtw5e6
- Hungarian by @npub1dnvslq0vvrs8d603suykc4harv94yglcxwna9sl2xu8grt2afm3qgfh0tp
- Polish by @npub16gjyljum0ksrrm28zzvejydgxwfm7xse98zwc4hlgq8epxeuggushqwyrm
