The global Apps feed (NIP-82 software apps) was sending a kinds-only REQ
filter with limit=200. zapstore's relay scores each REQ filter for
specificity and rejects anything that scores below 3, treating a kinds-only
filter with a limit >= 100 as "too vague" — so the whole subscription was
refused.
- Drop the global Apps filter limit from 200 to 99 so a kinds-only filter
clears the bar (kinds +1, limit < 100 +2 = 3).
- Always include wss://relay.zapstore.dev in the global Apps feed relays,
since it indexes the full app catalog, unless the user has added it to
their blocked relay list (NIP-51 kind 10006).
- Re-evaluate the subscription when the blocked relay list changes so
blocking/unblocking zapstore takes effect without an app restart.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01US2aK3igzfeo4xtcvg3mKc
innerLoadCurrentAccountFromEncryptedStorage awaited ~27 parallel parse
Deferreds directly inside the single 70-argument AccountSettings(...)
constructor expression. Each await is a suspension point, so the coroutine
state machine had to spill and restore the entire partially-evaluated
operand stack (dozens of already-built MutableStateFlow args) at every one
of them. That inflated the generated invokeSuspend method past the JVM
per-method limit:
Method exceeds compiler instruction limit: 57866 in
LocalPreferences$innerLoadCurrentAccountFromEncryptedStorage$result$1.invokeSuspend
Resolve every Deferred into a local val first, then build AccountSettings
as a single straight-line, suspension-free expression. Each await now sits
at a statement boundary with a near-empty operand stack, so only a small,
linear set of locals is spilled. Parallel parsing behaviour is unchanged.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01H6VdHHFRNYXKXFcW7j5sF7
Android's AAPT2 resource processor only recognizes a small set of valid
escape sequences: \, \', ", \n, \t, \r, \@, \?. All other \X sequences
(backslash-space, \-, \., \(, \), $, etc.) render as a literal backslash
followed by the character in the UI.
These were introduced by the batch AI translation. Remove the backslash
before every character that does not need escaping across 38 locale files.
The appended translation blocks from the previous batch-translation session
overlapped with existing Crowdin translations already present in the files,
causing 31 duplicate keys in values-hu-rHU and 3 in values-sl-rSI.
Keep the first (original Crowdin) occurrence of each key.
AGP 9.2.1 treats resources defined in both an app module and a library module
with the same key (qualifiers="") as an error in non-debug builds.
`browser_address_hint`, `browser_console_title_short`, `browser_console_title`,
`browser_console_clear`, and `napplet_untitled` were defined in both
`:amethyst/values/strings.xml` and `:nappletHost/values/strings.xml`.
Both `:amethyst` and `:nappletHost` depend on `:commons`, so the canonical
home for these shared strings is `commons/src/androidMain/res/values/strings.xml`.
Update callers in both modules to use `com.vitorpamplona.amethyst.commons.R as
CommonsR`. Locale translations in amethyst's `values-*/` directories remain as
Android resource overlays (app module overrides library module at merge time).
Fixes: Found item String/browser_console_clear more than one time (packageFdroidBenchmarkResources)
The previous commit incorrectly removed browser_address_hint,
browser_console_clear, browser_console_title, browser_console_title_short,
and napplet_untitled from values/strings.xml. These are needed for
amethyst's own browser and napplet screens — both amethyst and nappletHost
use them independently, and Android allows app-level strings to coexist
with same-named library strings (app wins).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Hfm5Lf3FfwDgPn5oiMVqUo
Remove 2959 strings from values-hu/strings.xml that were fully duplicated
in values-hu-rHU/strings.xml (added by prior commit), causing aapt2 to
error on `browser` and all other keys for the hu-HU build target.
Remove browser_console_clear, browser_address_hint, browser_console_title,
browser_console_title_short, and napplet_untitled from the app's default
values/strings.xml — these are already defined in the :nappletHost library
module, and having them in both causes aapt2 duplicate-key errors when the
library resources are merged into the app. Locale translations in
values-*/strings.xml continue to override the library defaults at runtime.
Fixes CI failures reported in PR #3371.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Hfm5Lf3FfwDgPn5oiMVqUo
266 string replacements across 41 locale files to reduce UI overflow risk.
Strings shortened to fit labels and menu items; technical terms (relay,
DM, NIP, Tor, pubkey, Cashu) kept as-is per project convention.
Languages updated: ar, cs, de, el, es, fa, fi, fr, hi, hu, in, it, nl,
pl, pt-BR, pt-PT, ru, sl, sv, sw, ta, th, uk, uz + regional siblings.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Hfm5Lf3FfwDgPn5oiMVqUo
- Escape unescaped `?` in calendar_rsvp_maybe_prefixed (it, ru, ru-rRU,
ru-rUA) — Android treats `?` at string start as a theme-attr reference
- Escape unescaped `@` in my_name (fi, tr, tr-rTR) and
quick_action_copy_user_id (zh) — same issue with `@` references
- Convert <plurals> to <string> for accounts_found, num_selected,
follow_accounts in ru/ru-rRU/ru-rUA — English source uses <string>,
so type-mismatched <plurals> were silently removed by AAPT2
- Fix orphaned key in el-rGR: error_parsing_json_from_lightning_
address_check_the_user_s_lightning_address_with_user renamed to
correct _setup_with_user suffix matching the English source
All locale files now build without errors or warnings.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Hfm5Lf3FfwDgPn5oiMVqUo
Fill all missing strings in Arabic (ar-rSA), Greek (el-rGR), Persian
(fa, fa-rIR), Finnish (fi-rFI), Indonesian (in, in-rID), Italian
(it-rIT), Japanese (ja, ja-rJP), Korean (ko-rKR), Russian (ru, ru-rRU,
ru-rUA), Thai (th, th-rTH), Turkish (tr, tr-rTR), Ukrainian (uk,
uk-rUA), Vietnamese (vi-rVN), and Traditional Chinese Taiwan (zh-rTW).
Most locale files now have all 2961 strings matching the English source.
Missing strings fall back to English per Android resource resolution.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Hfm5Lf3FfwDgPn5oiMVqUo
The single-zap/single-nutzap branch of MultiSetCompose dropped the bare
RenderLnZap/RenderNutzap activity card straight into the feed, with no
author column on the left, so it didn't read like the surrounding notes.
Reuse NoteCompose on the zap-receipt note instead: it wraps the same zap
card as the note body and gives it the standard note chrome — author
picture on the left, username header, reactions row. Padding is handed to
NoteCompose for this branch so it isn't doubled against the card column.
NoteCompose's author column previously resolved baseNote.author, which for
a kind-9735 zap receipt is the recipient's lightning provider (the signer),
not the zapper. Resolve the sender from the embedded zap request the same
way the thread's master note already does, so the picture on the left is
the actual zapper.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Cg8mHZDCfGmGAVDaeJxPFz
The outlined service gem was authored at ~half the 512x512 viewport size,
so it rendered noticeably smaller (both width and height) than every other
notification small icon, which fill the viewport. Wrap the path in a group
that scales it ~1.8x about its centre, leaving a margin so the stroke stays
within bounds.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SVA2C5FyYWSmU7EzdFaAD2
NutzapEvent was missing from the textNoteCards exclusion filter in
convertToCard, so every nutzap was both grouped into the MultiSetCard /
NutzapUserSetCard path AND fell through to a standalone NoteCard, which
renders kind 9321 as a big RenderNutzap card. The result was a duplicate:
the same nutzap appeared once in the grouped card and once as an
individual note — the way a reply would.
LnZapEvent is already excluded for exactly this reason; nutzaps were
simply overlooked when the nutzap grouping was added. Exclude NutzapEvent
too so it only renders through the grouped path.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01X8ZruubvykNhfmrJXJA8s7
A MultiSetCard bundles many notifications on a post into one compact stream
of icons, which is great when there are several. But a freshly-arrived zap
is usually alone in its own card (the additive feed path builds a
single-item MultiSetCard, and only a full rebuild groups same-post
notifications together), so it rendered as one tiny gallery icon.
When a MultiSetCard carries a single lightning zap or nutzap and nothing
else, render the existing large activity card (RenderLnZap / RenderNutzap)
— the same big, gradient "appreciation" display already used for onchain
zaps and the thread view — instead of the one-icon gallery. As soon as a
rebuild groups several notifications onto the same post (size > 1), it
falls back to the compact gallery as before.
This is purely a rendering decision in MultiSetCompose; the card-building
and grouping logic is unchanged.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01X8ZruubvykNhfmrJXJA8s7
The cashu outline icon next to author avatars in the nutzap galleries
rendered in the default content color, while the lightning equivalent
(ZappedIcon) uses BitcoinOrange. Tint the cashu mark BitcoinOrange in the
reaction-row gallery (NutzapGallery), the notification multi-set gallery
(MultiSetCompose), and the notification user-set card (NutzapUserSetCompose)
so nutzaps read as value transfers consistent with lightning zaps.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QEFq9cUPECuB8F56sTtWXu
The cashew + pixel-shades mark was visually weighted to the right of the
24x24 viewport. Shift every x-coordinate in both paths (the stroked cashew
body outline and the filled pixel sunglasses) by -2.4 units so the drawing
sits balanced within the icon bounds. Shape is unchanged; only position.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QEFq9cUPECuB8F56sTtWXu
- OnionLocationCache: add 24-hour TTL so stale .onion mappings expire
instead of permanently breaking Tor connectivity when a server rotates
its onion address
- OnionLocationInterceptor: demote from network to application interceptor
so chain.request().url.host is always the original clearnet hostname.
As a network interceptor it ran after OnionUrlRewriteInterceptor had
already rewritten the host to .onion, causing cache refreshes from
onion-routed responses to be stored under the wrong key and never used
- OnionUrlRewriteInterceptor: make http/https scheme mapping explicit and
symmetric with the ws/wss arm; document that http:// onion for an
https:// clearnet host is intentional (Tor circuit provides E2E security)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BkQwqqjjDJmeAChYRK4tuV
Documents that OnionUrlRewriteInterceptor's runCatching { toHttpUrl() }
call succeeds for Tor v2/v3 .onion hosts so a future OkHttp upgrade that
breaks .onion parsing is caught before the feature silently regresses.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BkQwqqjjDJmeAChYRK4tuV
Adds three components wired into both OkHttp factory chains (relays and
media/NIP-11):
- OnionLocationCache: ConcurrentHashMap<host, onionUrl> shared across
all OkHttp clients. No Nostr-specific protocol changes needed.
- OnionLocationInterceptor (network interceptor, always active): reads
the Onion-Location HTTP header from every response — WebSocket 101
handshakes, NIP-11 documents, image servers, anything — and populates
the cache. Discovery is a zero-cost by-product of traffic that already
happens.
- OnionUrlRewriteInterceptor (application interceptor, Tor clients only):
on outbound requests, checks the cache for the target host and rewrites
to the .onion address when available. Derives the correct ws/wss scheme
from the cached Onion-Location URL scheme so TLS expectations match.
First connection goes clearnet (or Tor-to-clearnet), populating the
cache. The next reconnect transparently uses the .onion address so Tor
connections avoid exit nodes entirely. Network transition reconnects
(already handled by RelayProxyClientConnector) retry with the cached
.onion on the fresh circuit.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BkQwqqjjDJmeAChYRK4tuV
HeaderRow's Row content lambda was generating MAXLOCALS=74, causing
GC overhead OOM in the Kotlin daemon during parallel compilation.
Extracting the Column block into a named HeaderRowTitle composable
reduces the Row lambda to two direct calls, cutting its bytecode size.
Co-Authored-By: Claude <noreply@anthropic.com>
The "locked nApplet vs open nSite" distinction was a `websiteMode: Boolean`
threaded through an Intent extra and re-branched at eight independent sites
across the launcher, the content server, both host surfaces, and the chrome.
That is one coupled security posture (capabilities, CSP, NIP-07 injection,
off-origin policy, network UI) expressed as scattered, drift-prone flags — and
boolean-blind, since the "website" is actually the *more* capable mode.
Introduce `HostProfile` (NAPPLET | WEBSITE), resolved once in the trusted main
process and carried over the Intent/Messenger boundary as its name. Every
coupled consequence now reads from one place:
- `declaredCapabilities(requires)` — THE broker grant, minted into the token
- `appCsp` / `injectsNip07` / `allowsOffOrigin` / `exposesNetwork`
Pure mechanical mapping (every branch 1:1, no behavior change). The wire extra
`EXTRA_WEBSITE_MODE` boolean becomes `EXTRA_HOST_PROFILE` string — safe, as it
is in-app process-to-process IPC, never persisted.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HrxLCMcQADUPnm8Sj9ZJ63
WalletTypeCard$lambda$0 (AddWalletScreen.kt) was generating a bytecode
method with MAXLOCALS=66, causing the JVM bytecode optimizer to exhaust
memory when three compile tasks ran in parallel on CI.
Extract the Card content into WalletTypeCardContent and extract the
add-recommendation item block in CashuMintRecommendationsScreen into
AddRecommendationSection (moving newRecommendationInput state there).
Both changes split large generated methods into smaller ones, reducing
per-task memory pressure in the Kotlin daemon.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MiKQ1CzErYcUBEbPrRudZQ
The in-app "app" surfaces had colliding, sometimes inaccurate names. NIP-89
native apps showed as "Apps" while the in-app favorites showed as "Favorite
apps", and the single-app host screens were named `FavoriteWebAppScreen` /
`FavoriteNappletScreen` even though they open any url/coordinate (favorited or
not) and the "Napplet" one also renders nSites.
Establish one taxonomy:
- Native app store -> "App Store" (NIP-89), in-app Nostr web client -> "Web app",
plus the existing nApplet / nSite. "Favorite" is now only the star toggle and
the pinned grid, not a screen.
- Code axis: `WebApp` (url-based) and `NostrApp` (coordinate-based nSite/nApplet).
The cross-process sandbox infra (`napplet/`, `nappletHost/`) keeps "Napplet".
Renames:
- Routes `FavoriteWebApp`/`FavoriteNostrApp` -> `WebApp`/`NostrApp`
- Screens `FavoriteWebAppScreen`/`FavoriteNappletScreen` -> `WebAppScreen`/`NostrAppScreen`
- Controllers `EmbeddedBrowserController`/`EmbeddedNappletController`
-> `EmbeddedWebAppController`/`EmbeddedNostrAppController`
- Factory `acquireBrowser`/`acquireNapplet` -> `acquireWebApp`/`acquireNostrApp`
- Model `FavoriteApp.WebUrl` -> `FavoriteApp.WebApp`; `WebUrlNetworkRegistry`
-> `WebAppNetworkRegistry`
- Strings: `software_apps`/`route_software_apps` "Apps" -> "App Store";
`favorite_apps_empty` reworded to name web app / nApplet / nSite
Persistence is untouched: favorite id prefixes ("url:"/"nostr:"), DataStore
names ("favorite_apps"/"weburl_network"), and serialized type tags ("url"/
"nostr") are all kept stable. Taxonomy documented in
amethyst/plans/2026-06-25-web-app-naming.md.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HrxLCMcQADUPnm8Sj9ZJ63
- TopControlSheet: replace SheetItem with SheetSwitchItem for the
console row, adding a Switch that reflects the current
consoleShowing state; add consoleShowing param and wire it from
EmbeddedTabLayer
- BottomConsoleSheet: move the grabber Column before AnimatedVisibility
so it sits at the top of the expanded panel (standard bottom-sheet
handle behaviour) and animates upward as the console opens
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GxQdArv6h6SezciUWeoR38
Reorder Account Settings: group network (relays/sync/follows, media/nests),
interactions (reactions/zaps), media playback, identity (DVMs/badges/payments),
then safety/i18n.
Reorder App Settings: group UI (privacy/prefs), composition, personalization
(home/reactions/bottom-bar/profile), then advanced (calendar/OTS/namecoin).
Also drop the now-redundant Build.VERSION_CODES.R gate on BROWSER in
DrawerNavigateItems, matching the fix landed in main (PR #3361).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AdbTQuhuk2g5XGWEqmKLRz
Move BROWSER to the Navigate section (between VIDEO and DISCOVER).
Reorganise Feeds into logical clusters: content types first, then
live/social, communities/chats, calendar, apps/tools, packs, misc.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AdbTQuhuk2g5XGWEqmKLRz