Commit Graph
15579 Commits
Author SHA1 Message Date
Vitor PamplonaandGitHub 3fa17e1cb7 Merge pull request #3346 from greenart7c3/claude/nip-2381-bunker-connect-psiq2t
NIP-46: Add optional client metadata to connect requests
2026-06-24 08:58:28 -04:00
Claude da4207319b fix(nip46): pin client metadata to 4th connect param, backfill empties
Per NIP-46 (nostr-protocol/nips#2381), client metadata is the 4th
positional connect param. When it is present but the optional secret or
permissions are not, those slots are now back-filled with empty strings
so the metadata always lands at index 3. Parsing maps empty placeholders
back to null. When no metadata is sent, the array stays as short as
possible for backward compatibility.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PYpupiVAq4VyHDdjrYyPdi
2026-06-24 12:08:06 +00:00
Claude 4ae2debd6b refactor(cli): drop client-metadata log from bunker connect handler
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PYpupiVAq4VyHDdjrYyPdi
2026-06-24 11:28:14 +00:00
David KasparandGitHub 2311a4cd1b Merge pull request #3345 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2026-06-23 19:06:44 +01:00
vitorpamplonaandgithub-actions[bot] 8ffc65d727 chore: sync Crowdin translations and seed translator npub placeholders 2026-06-23 17:00:04 +00:00
Vitor PamplonaandGitHub ff2c6f6313 Merge pull request #3344 from davotoula/fix/markdown-detect-heading-after-blank-line
fix: render markdown when a heading/list follows a blank line
2026-06-23 12:58:14 -04:00
davotoulaandClaude Opus 4.8 a97529d7af fix: detect markdown when a heading/list follows a blank line
isMarkdown() treated the second newline of a blank line as a non-space
character, flipping the line-start tracker off. ATX headings, blockquotes,
and list markers that follow the standard blank-line spacing went
undetected, so NIP-23 long-form articles made of prose plus section
headings rendered as raw text. Exclude newline/carriage-return from the
line-start guard so a blank line stays at line start.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-23 18:23:22 +02:00
Claude 55bc75512a feat(nip46): support optional client metadata in connect request
Implements nostr-protocol/nips#2381: a client MAY attach an optional
4th positional parameter to the NIP-46 `connect` request carrying a
JSON-stringified `{name, url, image}` object, mirroring the fields
already present in `nostrconnect://` URIs. This lets a bunker:// paired
signer show who is asking to connect.

- quartz: add BunkerClientMetadata and a clientMetadata field on
  BunkerRequestConnect; serialize it as the 4th param (omitted when
  empty) and parse it back, degrading malformed/empty JSON to null.
- quartz: NostrSignerRemote carries and sends clientMetadata on
  connect() and threads it through fromBunkerUri().
- commons: BunkerLoginUseCase.execute() accepts optional clientMetadata.
- desktopApp: advertise Amethyst's metadata on bunker login.
- cli: the receiving bunker logs the connecting client's identity
  (display-only; never gates the ACK on it, since the client pubkey is
  unauthenticated in bunker:// pairing).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PYpupiVAq4VyHDdjrYyPdi
2026-06-23 16:17:19 +00:00
David KasparandGitHub 7d77a057d6 Merge pull request #3343 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2026-06-23 17:16:18 +01:00
davotoulaandgithub-actions[bot] dd062861f3 chore: sync Crowdin translations and seed translator npub placeholders 2026-06-23 16:14:26 +00:00
David KasparandGitHub 9c1392a2e0 Merge pull request #3341 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2026-06-23 17:12:37 +01:00
vitorpamplonaandgithub-actions[bot] 832951b90c chore: sync Crowdin translations and seed translator npub placeholders 2026-06-23 15:58:03 +00:00
Vitor Pamplona b7cbc03b48 Fixes the compilation error 2026-06-23 11:55:40 -04:00
Vitor Pamplona bf10498caf Merged 2026-06-23 11:50:36 -04:00
Vitor Pamplona 1eca812aec Fixes tests 2026-06-23 11:08:24 -04:00
Vitor Pamplona 1365be12a5 Fixes tests 2026-06-23 11:05:04 -04:00
Vitor PamplonaandGitHub f6ea3e1999 Merge pull request #3342 from vitorpamplona/claude/funny-cannon-txzzqf
Load author relay lists for missing addressable notes
2026-06-23 09:58:49 -04:00
Vitor PamplonaandGitHub 3b969e4b71 Merge pull request #3335 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2026-06-23 09:44:56 -04:00
Claude 128eed4a25 refactor: reuse UserFinderFilterAssembler instead of duplicating relay-list logic
Replace the self-contained AddressableAuthorRelayLoaderSubAssembler
(which duplicated UserOutboxFinderSubAssembler) with a thin bridge that
injects UserFinderQueryState entries directly into the existing
UserFinderFilterAssembler.

When EventFinderFilterAssembler detects an AddressableNote stub whose
author relay list is unknown, it subscribes that author to userFinder.
UserOutboxFinderSubAssembler already handles the kind-0/10002 fetch and
relay resolution — no logic is duplicated. Subscriptions are cleaned up
when the note loads or the EventFinder key is removed.
2026-06-23 13:34:15 +00:00
vitorpamplonaandgithub-actions[bot] 82a20c012a chore: sync Crowdin translations and seed translator npub placeholders 2026-06-23 03:10:29 +00:00
Vitor PamplonaandGitHub 402439f09f Merge pull request #3340 from vitorpamplona/claude/github-ci-failures-yja302
fix(build): resolve :nappletHost benchmark variant via matchingFallbacks
2026-06-22 23:08:53 -04:00
Claude 1f4ff91419 fix(build): give :nappletHost a benchmark variant for :amethyst benchmark builds
The new plain-AGP :nappletHost library only declared debug/release build
types (unlike the KMP :commons/:quartz/:nestsClient libs, which match any
build type). The :amethyst app's `benchmark` build type therefore had no
matching :nappletHost variant, breaking fdroidBenchmark/playBenchmark
resolution and Test/Build CI on main.

Declare a matching `benchmark` build type (initWith release) in :nappletHost
so the app resolves a real variant. Library AARs aren't signed, so no
signing config is needed here. :nappletHost is the only plain-AGP library
:amethyst depends on, so no consumer-side matchingFallbacks is required.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014tzXcqVUcAPhyDDFak4SQ8
2026-06-23 02:50:48 +00:00
Claude 4574005019 feat: fetch author relay list when addressable event is missing
When a kind-16 repost (or any event) has an a-tag pointing to an
addressable event by an unknown author (no NIP-65 relay list loaded),
potentialRelaysToFindAddress() returned an empty set and the event was
never found.

AddressableAuthorRelayLoaderSubAssembler mirrors
UserOutboxFinderSubAssembler but is driven by EventFinderQueryState.
It detects AddressableNote stubs whose author relay list is missing and
emits kind-0/10002 filters to indexer/search relays. On EOSE it calls
invalidateFilters(), causing NoteEventLoaderSubAssembler to re-run with
the now-populated outbox relays and fetch the addressable event.
2026-06-23 01:46:58 +00:00
Claude 0866d38065 fix(build): resolve :nappletHost benchmark variant via matchingFallbacks
The new plain-AGP :nappletHost library only declares debug/release build
types (unlike the KMP :commons/:quartz libs, which match any build type).
The :amethyst app's `benchmark` build type had no fallback, so Gradle could
not resolve a matching :nappletHost variant for fdroidBenchmark/playBenchmark,
breaking Test/Build CI on main.

Add `matchingFallbacks += "release"` to the benchmark build type so it falls
back to a library's release variant when no benchmark variant exists.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014tzXcqVUcAPhyDDFak4SQ8
2026-06-23 01:21:31 +00:00
Vitor PamplonaandGitHub 068e12b5f6 Merge pull request #3339 from vitorpamplona/claude/awesome-pasteur-xwiwad
Implement NIP-5A/5D napplet & nsite sandbox host with capability broker
2026-06-22 20:46:16 -04:00
Claude 2b80bf49e3 feat(napplet): broker the NAP inc topic bus
Adds the `inc` pub/sub bus so napplets that declare it boot and can exchange
topic events: `inc.subscribe {topic}` / `inc.unsubscribe {topic}` register
interest, `inc.emit {topic, payload}` fans out an `inc.event {topic, payload,
sender}` to OTHER subscribed napplet sessions (never echoing the sender) — the
kehto runtime's inc contract.

- Router edge ops (gated on the INC declaration alone, like identity.watch —
  no per-call consent): SubscribeInc/UnsubscribeInc/EmitInc outcomes.
- Protocol: readTopic/readPayloadRaw + encodeIncEvent.
- NappletIncBus in the broker service routes across the live napplet sessions
  (the one service every sandbox binds), keyed by reply Messenger.
- Tests for inc routing + declaration gating; updated capability/router tests
  that asserted the old "inc/theme/notify are unknown" behavior.

NOTE: napplets run foreground-only/one-at-a-time, so cross-napplet delivery is
usually a no-op in practice; the bus is correct if sessions ever overlap. It is
app-wide (not author-scoped) — a future refinement could namespace topics by
author. Unblocks feed/profile-viewer/chat/bot. See the plan doc.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016ncMHuBBVHEf7spAoSssde
2026-06-23 00:37:44 +00:00
Claude 1599f34308 feat(napplet): broker the NAP notify domain
Napplets can now create/list/dismiss user-facing notifications:
`notify.create { title, body }` → `notify.created { id }` (the bespoke
past-tense reply the client listens for, not the generic `.result`),
`notify.list` → `notify.listed { notifications }`, `notify.dismiss` fire-and-forget.

- NotifyCreate/NotifyList/NotifyDismiss requests, NotifyCreated/NotifyListed
  responses, NappletNotifyGateway + NappletNotification; broker executes them
  (consent-gated, ask-once).
- Protocol encodes the past-tense reply types.
- Android: NappletNotificationStore (per-coordinate, main-process, survives
  broker rebuilds — a napplet only ever sees/dismisses its own) + a best-effort
  system-tray notification.
- Consent summary + string for notify.

Unblocks the toaster demo. See the plan doc.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016ncMHuBBVHEf7spAoSssde
2026-06-23 00:30:42 +00:00
Claude 4b8b2ac513 feat(napplet): broker the NAP theme domain
Napplets can now read the host's current theme via `theme.get` →
`theme.get.result { theme: { colors: { background, text, primary } } }`,
mapping it to their CSS variables. This is the universal boot gate for
real-world napplets (e.g. kehto/web's demos all `requires: theme` and abort
if shell.supports('theme') is false).

- NappletCapability gains THEME (+ NOTIFY/INC, wired in following commits);
  adds requiresConsent (false for SHELL/THEME — cosmetic/negotiation never prompt).
- ThemeGet request, Theme response, NappletThemeGateway; broker executes it
  with no consent prompt.
- Android gateway returns Amethyst's brand purple with a dark/light bg+text pair.
- Capability label/description/icon + strings for theme/notify/inc.

See amethyst/plans/2026-06-23-napplet-nap-theme-notify-inc.md.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016ncMHuBBVHEf7spAoSssde
2026-06-23 00:23:26 +00:00
Claude 20b42d3443 fix: prefetch off main thread; use real Tor logo in sandbox bar
- StrictMode DiskReadViolation: PrefetchManifestBlobs read context.cacheDir
  (ensurePrivateCacheDirExists touches disk) on the composition dispatcher.
  Move it into withContext(Dispatchers.IO); the prefetch was already IO-bound.
- Sandbox top-bar network indicator now uses the app's real Tor logo
  (ic_tor, copied into :nappletHost since it can't depend on :amethyst) via an
  ImageView, instead of the onion/globe emoji. Lit when routing through Tor,
  dimmed when the site loads over the open web.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016ncMHuBBVHEf7spAoSssde
2026-06-22 23:30:30 +00:00
Claude 7eb0949e5c feat: route nSite WebView traffic through Tor by default, per-site opt-out
nSites now load over Tor by default when Tor is active — closing the gap where
only blob fetches were Tor-routed while the site's own web traffic (fetch/img/
script) went out the system network and could leak the user's IP. Users can opt
a specific site out to the open web (e.g. a site that breaks or is slow over
Tor); the choice is remembered per site and makes everything for that site
direct (web + blobs).

- NappletHostActivity sets a process-wide WebView SOCKS proxy override
  (socks5://127.0.0.1:<torPort>) for website-mode nSites, or clears it for open
  web. Best-effort + on-device-verifiable: SOCKS-over-WebView support varies by
  WebView version, so it's isolated to applyWebViewProxy() and never breaks the
  site if unsupported.
- Top-bar onion (🧅 Tor / 🌐 open web) shows the routing and toggles it; the
  dialog explains the IP-privacy trade. Shown only for nSites when Tor is active.
- NappletNetworkRegistry: main-process per-site preference (coordinate-keyed,
  DataStore-backed, Tor-default). The launcher reads it; the broker persists the
  sandbox's toggle resolved through the launch token. The key-free sandbox never
  touches it.
- Toggling persists via a new MSG_SET_NETWORK_MODE IPC, then relaunches the host
  so the proxy + content server rebuild cleanly for the new mode.
- "Open web" also routes blob fetches direct (effective proxy -1); locked
  napplets always keep Tor for blobs (no toggle exposed).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016ncMHuBBVHEf7spAoSssde
2026-06-22 23:10:12 +00:00
Vitor PamplonaandClaude Opus 4.8 d0d1577bfe fix(napplets): remove the stray white band at the bottom of the applet WebView
Two edge-to-edge issues on the napplet/nsite host, both surfacing as a white strip below
the applet on a light page:

- Double bottom inset. The root inset listener turned the system-bar/cutout insets into
  padding but returned them un-consumed, so the child WebView (which on targetSdk 35+
  auto-applies any insets it receives to its web content) padded the bottom a SECOND time.
  Zero those types before they reach the WebView, keeping IME flowing so keyboard resize
  still works.

- Over-scroll reveal. Forcing a scroll past the content edge stretched the view and
  exposed the shell document's background behind the applet iframe. WebView.overScrollMode
  only governs the outer frame, so also inject `overscroll-behavior: none` into the applet
  document (alongside the shim) to kill the stretch at the source, theme-independently.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-22 19:01:42 -04:00
Claude c7d876096f feat: NIP-07 window.nostr provider for nSites
nSites now open in "website mode": a normal web app with normal network
access plus a NIP-07 window.nostr provider, so standard Nostr web apps can
"log in with Amethyst" and sign as the active user. Napplets are unchanged
(locked, declared-only sandbox).

- window.nostr (shim.js) installs only when the host sets __nappletNip07
  (website mode). getPublicKey/getRelays reuse the existing consent-gated
  identity reads; signEvent is a new sign-only op honoring the app-supplied
  created_at (no publish — the web app sends to relays itself).
- NappletRequest.SignEvent + nostr.signEvent decode; broker signs as the
  user and returns the signed event without publishing. pubkey is still
  fixed by the signer, so the app can never sign as another identity.
- Website mode: content server defers off-origin requests to the WebView
  and drops the app CSP (normal network); locked napplets keep connect-src
  'none' and 404 off-origin.
- Launcher grants IDENTITY + RELAY (consent-gated) for website mode,
  independent of the nSite's empty manifest requires.
- Consent dialog shows the kind + content preview for a sign request.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016ncMHuBBVHEf7spAoSssde
2026-06-22 22:49:58 +00:00
Claude 5e4250bfbc fix(napplets): expand the "what it can access" section straight down, not from the left
The disclosure used AnimatedVisibility's default transition and the inner servers
Column didn't fill width, so the section faded/expanded while the server list also
grew in horizontally from the left — an inconsistent, weird effect. Make the
transition explicit (fade + expandVertically/shrinkVertically anchored at Top, so
it opens/closes straight down like the card) and fill width on the servers column
so nothing slides in sideways.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016ncMHuBBVHEf7spAoSssde
2026-06-22 22:26:47 +00:00
Claude 42edc13be2 feat(napplets): back gesture pages back in the WebView, then exits to Amethyst
The sandbox host now routes the back gesture into the applet/site's own history
first: an OnBackPressedCallback calls webView.goBack() while there's history to
pop (in-page links, iframe navigations, and history.pushState all count), and
only disables itself — letting system back return to Amethyst — once the WebView
is at its first page. The callback's enabled state tracks canGoBack() via
doUpdateVisitedHistory / onPageFinished. Works with predictive back.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016ncMHuBBVHEf7spAoSssde
2026-06-22 22:23:36 +00:00
Vitor PamplonaandClaude Opus 4.8 a66821d59e docs(napplets): record the per-applet storage-origin change in the security plan
Document the move from the opaque `allow-scripts`-only sandbox to a real per-applet
origin (https://<sha256(author:identifier)>.napplet.local, allow-same-origin) under
"Fixed in this pass", plus the residual risks it introduces: the load-bearing invariant
that the applet origin must stay distinct from the shell/bridge origin, persistent client
storage as a new persistence/exfil surface, the origin-id derivation, and service workers
now being reachable-but-unwired.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-22 18:20:57 -04:00
Vitor PamplonaandClaude Opus 4.8 53d6a2f8b4 fix(napplets/nsites): render real SPAs in the sandbox (blank-page + reload-loop)
Static-site / SPA nApplets & nSites (Vite/nsyte/CRA/webpack output) rendered as a
blank page, then — once that was fixed — as a fast reload-loop blink. Three layered
causes, each found on-device via logcat:

1. Sub-path serving. The applet loaded under https://napplet.local/app/, but bundlers
   emit absolute asset URLs (/assets/app.js, /fonts/…) that resolve against the origin
   ROOT, so every script/style/font 404'd. nSites are defined to be hosted at the domain
   root; serve there.

2. Opaque-origin storage. The applet ran in an `allow-scripts`-only iframe, so its origin
   was opaque ("null"): module scripts + asset fetches were CORS-blocked, and reading
   localStorage/IndexedDB/serviceWorker threw SecurityError — which crash-loops every SPA
   (gruuv: "cache version 0 < 23 → reset → reload", forever, because IndexedDB never
   worked so the version never persisted).

Fix: give each applet its OWN real, persistent, isolated origin — a per-applet subdomain
https://<id>.napplet.local (id = sha256(author:identifier)), framed by the shell with
`allow-scripts allow-same-origin`. A real origin restores localStorage/IndexedDB/SW and
makes the applet's own assets same-origin (no CORS). Isolation is preserved because the
origin is DISTINCT from the shell's: the native bridge stays origin-restricted to the
shell (napplet.local), so the cross-origin applet still can't reach it or read the shell
DOM, and per-applet subdomains keep applets' storage isolated from each other. The shell
HTML's iframe src + CSP frame-src are bound to the specific applet origin at serve time.

Also add an in-memory localStorage/sessionStorage polyfill to the injected shim as
belt-and-suspenders for any context where DOM storage is still unavailable.

By-design sandbox enforcement is unchanged and correctly blocks the rest (external CDN
scripts, direct relay WebSockets via connect-src 'none', external images) — apps must go
through the napplet SDK for those.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-22 18:18:56 -04:00
Claude 176c96ef01 perf(napplets): faster opens — overlap WebView init, parallel prefetch, CAS fast-serve
Three load-time wins on the nApplet/nSite open path:

1. Overlap WebView/Chromium init with the index probe. The host now creates +
   warms the WebView (and binds the broker) in onCreate, so its slow first-in-
   process init runs concurrently with the IO availability probe instead of
   serially after it; the probe just attaches the ready WebView.
2. Prefetch in parallel, index-first. NappletBlobPrefetcher downloads a
   manifest's blobs with bounded concurrency (5) and fetches index.html first,
   instead of strictly sequentially — faster first paint on cold opens.
3. Serve CAS hits without re-hashing. The content server short-circuits a
   content-addressed cache hit (verified on write, addressed by sha256) straight
   to the response, skipping the resolver's per-serve sha256 over the whole blob
   — the dominant CPU cost for large bundles. Network (cache-miss) path still
   fully verifies.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016ncMHuBBVHEf7spAoSssde
2026-06-22 22:07:09 +00:00
Claude 31bd34e838 feat(napplets): prefetch blobs to disk on render + loading/unavailable screen
Faster opens: while an nApplet/nSite card is on screen, eagerly download +
sha256-verify all of its blobs (Tor-routed) into a shared content-addressed
cache, so tapping Open serves from disk. Prefetch is de-duplicated (in-flight +
on-disk) and cancellation-aware (stops when the card scrolls away).

- New :nappletHost pieces: NappletBlobCache (content-addressed, multi-process-safe
  atomic store, replacing the single-process OkHttp DiskLruCache), NappletBlobHttp
  (shared Tor OkHttp + size-capped download), NappletBlobPrefetcher.
- NappletContentServer now reads the shared cache first, falling back to a Tor
  download that refills it; still re-verifies every blob on serve.
- Cards trigger prefetch via a LaunchedEffect in the StaticWebsite render fns.

Nicer launch: NappletHostActivity probes the index (resolving + caching it)
behind a loading screen (monogram + title + spinner), then shows the app, or a
clear "couldn't load — try again" screen if the publisher's servers are offline.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016ncMHuBBVHEf7spAoSssde
2026-06-22 21:32:48 +00:00
Vitor PamplonaandClaude Opus 4.8 4b838fec72 feat(napplets/nsites): link browse feeds to the TopNav filter at the relay level
The nApplet (NIP-5D) and nSite (NIP-5A) browse feeds ignored the top-nav
follow-list selection at the relay layer: a single SingleSubEoseManager fired a
hardcoded all-authors REQ against the user's home relays, and the screen merely
re-filtered the dump client-side. Selecting Follows / a People-list never changed
what was fetched, so manifests on an author's own write relays were invisible.

Bring them to Pictures-style parity:

- Replace the SingleSubEoseManager with PerUserAndFollowListEoseManager, watching
  defaultX FollowList + the new liveX FollowListsPerRelay outbox flow and rebuilding
  the per-relay REQ on change.
- Add makeN{applets,sites}Filter dispatchers + subassemblies (Global, Authors,
  Follows, MutedAuthors). These manifests carry no topical tags, so the tag-based
  selections (hashtag/geohash/community) correctly fall through to no subscription.
- Add liveNappletsFollowListsPerRelay / liveNsitesFollowListsPerRelay (OutboxLoaderState).
- New authorOnlyRoutes spinner option set: author-based filters only (no geohash,
  hashtag, community, relay, interest-set or AroundMe — none can match these events).
- "Mine" is intercepted per-feed (SubAssembler + screen), querying the user's own
  pubkey against their outbox relays — the shared TopFilter.Mine flow resolves to
  all-follows, so it can't be used directly.

Fix a FATAL duplicate-key crash: these replaceable/addressable kinds were observed
through the versioned note store (observeEvents) and keyed by per-version event id.
Switch to observeNotes, which yields one AddressableNote per address (latest
version), keyed by the stable address (idHex) and auto-updating in place.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-22 17:06:43 -04:00
Claude 4218a7c1af refactor(napplets): extract sandbox runtime into :nappletHost module
The `:napplet` sandbox runtime (NappletHostActivity, NappletContentServer,
NappletIpc, NappletKeyActions) now lives in a new :nappletHost Android library
that depends only on :commons + :quartz — NEVER :amethyst. So the sandbox code
is compile-time incapable of importing Amethyst.instance / LocalCache / Account,
turning the "two-process, no secrets in the sandbox" rule from a convention into
a build-graph guarantee.

- New module + NappletHostContract (Intent-extra keys + broker service FQN), so
  the launcher (amethyst) and activity (module) share the launch contract with no
  dependency cycle. The activity binds the broker by class name.
- Capability labels for the "what it can access" sheet are resolved by the
  launcher (which has app resources) and passed in, so the module needs no
  capability string resources. Host-only strings moved into the module.
- amethyst depends on :nappletHost; the broker-side (NappletBrokerService,
  gateways, NappletLaunchRegistry) stays in :amethyst. Manifest declares the
  activity by FQN (keeps @style/Theme.Amethyst resolvable).
- Docs updated (CLAUDE.md + security plan).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016ncMHuBBVHEf7spAoSssde
2026-06-22 20:45:19 +00:00
Claude 55cbb72a99 docs(napplets): document the two-process model for future contributors/AIs
Add a heads-up to the Amethyst Application KDoc and .claude/CLAUDE.md that the
app runs in two OS processes (main + :napplet), that Android reuses the single
Application class in both, and that statics/objects (Amethyst.instance,
LocalCache, NappletLaunchRegistry) are per-process — so code must not assume
`instance` exists off the main process or share singletons across the boundary.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016ncMHuBBVHEf7spAoSssde
2026-06-22 20:18:34 +00:00
Claude cdd5e916c5 fix(napplets): don't crash the :napplet process on memory-trim
Android instantiates the single Amethyst Application in every process, so the
sandbox (`:napplet`) shares it and onCreate early-returns there, leaving the
lateinit `instance` (AppModules) unset. But onTrimMemory — delivered to every
process on real devices — and onTerminate called `instance` unconditionally,
crashing the sandbox with UninitializedPropertyAccessException on a trim.
Gate both with the cached sandbox-process check.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016ncMHuBBVHEf7spAoSssde
2026-06-22 20:08:20 +00:00
Claude 6d367526a5 feat(nsites): dedicated nSites browse feed (mirrors nApplets)
Add a bottom-nav nSites feed for NIP-5A static sites (kinds 15128/35128),
mirroring the nApplets feed: a follow-list FeedFilterSpinner in the top bar
(persisted as defaultNsitesFollowList + liveNsitesFollowLists author-matcher)
and rows rendered through the shared NoteCompose path, so they reuse the author
header, StaticWebsiteCard (with Open), and reaction bar.

New: NsitesScreen, NsitesTopBar, the Nsites discovery datasource trio, Route.Nsites,
NavBarItem.NSITES. Wired into AppNavigation, the relay coordinator, and the
follow-list settings persistence.

Also includes the earlier display rebrand to nApplet/nSite.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016ncMHuBBVHEf7spAoSssde
2026-06-22 20:03:44 +00:00
Claude 8c4eae6f5a chore(napplets): rebrand display text to "nApplet" / "nSite"
User-facing strings only — "Napplet(s)" → "nApplet(s)" and the static-site
label → "nSite". Code identifiers, resource keys, CLI verbs (amy napplet/nsite),
and unrelated profile "Website" labels are untouched.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016ncMHuBBVHEf7spAoSssde
2026-06-22 19:52:00 +00:00
Vitor PamplonaandGitHub e5ee09f060 Merge pull request #3337 from vitorpamplona/claude/peaceful-mendel-drkneu
Show blocked/muted user indicator on profile tabs
2026-06-22 15:43:50 -04:00
Claude 8ad5244c1e feat: show blocked/muted state instead of empty feed on profile
When viewing the Notes or Replies tab of a muted/blocked user, display a
"UserBlockedFeed" screen with an Unblock button rather than loading the
LazyColumn feed (which would show only dividers after filtering removes all
hidden posts).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ChNCLiDX58paTKhReYCYq6
2026-06-22 19:23:40 +00:00
Claude ab2ca77998 fix(napplets): harden the sandbox trust boundary (identity, private lists, visibility)
Three security hardenings from the nsite/napplet review:

1. Launch-token identity binding. The broker no longer trusts the identity +
   declared capabilities sent on each IPC message from the (less-trusted)
   :napplet process. The main process now mints a random token at launch
   (NappletLaunchRegistry), hands only that to the sandbox, and resolves it back
   to the trusted identity/declared set. A compromised sandbox can act only as
   the napplet it was launched as — closing cross-napplet coordinate spoofing
   (storage + permission ledger).

2. Stop leaking private lists. identity.getMutes/getBlocked read the decrypted
   flow, which includes the user's PRIVATE mutes/blocks. Return only the events'
   public tags (MuteListEvent.publicMutes / PeopleListEvent.publicUsersIdSet).

3. Make grants visible + anti-phishing chrome. A persistent trusted sandbox bar
   (shield + name + tap for "what it can access") the applet can't draw over, and
   a live toast when a granted publish/upload/payment runs — so an allow-always
   grant can't act silently. Also keeps the host out from under the system bars.

Findings + residual risks (session-scoped grants, per-origin resource consent)
tracked in amethyst/plans/2026-06-22-napplet-nsite-security.md.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016ncMHuBBVHEf7spAoSssde
2026-06-22 18:47:19 +00:00
Vitor PamplonaandClaude Opus 4.8 6a07d21a23 Merge upstream/main into claude/awesome-pasteur-xwiwad
Brings in apps-feed-card 3-dot options menu (upstream #3336).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-22 14:37:06 -04:00
Vitor PamplonaandClaude Opus 4.8 d01455f3c9 Merge remote napplet updates into local fix branch
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-22 14:29:24 -04:00
Vitor PamplonaandClaude Opus 4.8 07d25c02db fix(napplet): serialize consent prompts so concurrent requests don't drop
When a napplet issued several consent-gated calls at once (the common case: it
reads relays + storage + identity on load), each launched a NappletConsentActivity
concurrently. The host can only show one, so the rest were delivered to the
single-top activity and silently dropped — their broker calls hung forever
(storage stuck pending; a subscription's consent lost, yielding 0 events).

Gate the consent-prompt path behind a Mutex on the (per-account, reused) broker
so prompts queue one at a time. After taking the lock, re-read the ledger so a
sibling request for the same capability honors the just-recorded grant instead
of prompting again. Only the prompt is serialized — execute() and already-granted
paths stay parallel. Per-use capabilities (payments) still re-prompt every time.

Adds a regression test asserting 5 concurrent same-capability requests yield
exactly one prompt and never two dialogs at once.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-22 14:28:33 -04:00